mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-15 13:55:19 +00:00
enh: move chat menu
This commit is contained in:
parent
f46837fd10
commit
d720e027ad
5 changed files with 160 additions and 31 deletions
|
|
@ -37,7 +37,8 @@
|
||||||
showArtifacts,
|
showArtifacts,
|
||||||
tools,
|
tools,
|
||||||
toolServers,
|
toolServers,
|
||||||
selectedFolder
|
selectedFolder,
|
||||||
|
pinnedChats
|
||||||
} from '$lib/stores';
|
} from '$lib/stores';
|
||||||
import {
|
import {
|
||||||
convertMessagesToHistory,
|
convertMessagesToHistory,
|
||||||
|
|
@ -55,8 +56,10 @@
|
||||||
getAllTags,
|
getAllTags,
|
||||||
getChatById,
|
getChatById,
|
||||||
getChatList,
|
getChatList,
|
||||||
|
getPinnedChatList,
|
||||||
getTagsById,
|
getTagsById,
|
||||||
updateChatById
|
updateChatById,
|
||||||
|
updateChatFolderIdById
|
||||||
} from '$lib/apis/chats';
|
} from '$lib/apis/chats';
|
||||||
import { generateOpenAIChatCompletion } from '$lib/apis/openai';
|
import { generateOpenAIChatCompletion } from '$lib/apis/openai';
|
||||||
import { processWeb, processWebSearch, processYoutubeVideo } from '$lib/apis/retrieval';
|
import { processWeb, processWebSearch, processYoutubeVideo } from '$lib/apis/retrieval';
|
||||||
|
|
@ -2118,6 +2121,27 @@
|
||||||
}
|
}
|
||||||
await sessionStorage.removeItem(`chat-input${chatId ? `-${chatId}` : ''}`);
|
await sessionStorage.removeItem(`chat-input${chatId ? `-${chatId}` : ''}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const moveChatHandler = async (chatId, folderId) => {
|
||||||
|
if (chatId && folderId) {
|
||||||
|
const res = await updateChatFolderIdById(localStorage.token, chatId, folderId).catch(
|
||||||
|
(error) => {
|
||||||
|
toast.error(`${error}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
currentChatPage.set(1);
|
||||||
|
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||||
|
await pinnedChats.set(await getPinnedChatList(localStorage.token));
|
||||||
|
|
||||||
|
toast.success($i18n.t('Chat moved successfully'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.error($i18n.t('Failed to move chat'));
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
@ -2192,6 +2216,8 @@
|
||||||
shareEnabled={!!history.currentId}
|
shareEnabled={!!history.currentId}
|
||||||
{initNewChat}
|
{initNewChat}
|
||||||
showBanners={!showCommands}
|
showBanners={!showCommands}
|
||||||
|
archiveChatHandler={() => {}}
|
||||||
|
{moveChatHandler}
|
||||||
onSaveTempChat={async () => {
|
onSaveTempChat={async () => {
|
||||||
try {
|
try {
|
||||||
if (!history?.currentId || !Object.keys(history.messages).length) {
|
if (!history?.currentId || !Object.keys(history.messages).length) {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@
|
||||||
export let showBanners = true;
|
export let showBanners = true;
|
||||||
|
|
||||||
export let onSaveTempChat: () => {};
|
export let onSaveTempChat: () => {};
|
||||||
|
export let archiveChatHandler: (id: string) => void;
|
||||||
|
export let moveChatHandler: (id: string, folderId: string) => void;
|
||||||
|
|
||||||
let closedBannerIds = [];
|
let closedBannerIds = [];
|
||||||
|
|
||||||
|
|
@ -165,6 +167,10 @@
|
||||||
shareHandler={() => {
|
shareHandler={() => {
|
||||||
showShareChatModal = !showShareChatModal;
|
showShareChatModal = !showShareChatModal;
|
||||||
}}
|
}}
|
||||||
|
archiveChatHandler={() => {
|
||||||
|
archiveChatHandler(chat.id);
|
||||||
|
}}
|
||||||
|
{moveChatHandler}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition"
|
class="flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition"
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,11 @@
|
||||||
temporaryChatEnabled,
|
temporaryChatEnabled,
|
||||||
theme,
|
theme,
|
||||||
user,
|
user,
|
||||||
settings
|
settings,
|
||||||
|
folders
|
||||||
} from '$lib/stores';
|
} from '$lib/stores';
|
||||||
import { flyAndScale } from '$lib/utils/transitions';
|
import { flyAndScale } from '$lib/utils/transitions';
|
||||||
|
import { getChatById } from '$lib/apis/chats';
|
||||||
|
|
||||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||||
import Tags from '$lib/components/chat/Tags.svelte';
|
import Tags from '$lib/components/chat/Tags.svelte';
|
||||||
|
|
@ -30,13 +32,19 @@
|
||||||
import Clipboard from '$lib/components/icons/Clipboard.svelte';
|
import Clipboard from '$lib/components/icons/Clipboard.svelte';
|
||||||
import AdjustmentsHorizontal from '$lib/components/icons/AdjustmentsHorizontal.svelte';
|
import AdjustmentsHorizontal from '$lib/components/icons/AdjustmentsHorizontal.svelte';
|
||||||
import Cube from '$lib/components/icons/Cube.svelte';
|
import Cube from '$lib/components/icons/Cube.svelte';
|
||||||
import { getChatById } from '$lib/apis/chats';
|
import Folder from '$lib/components/icons/Folder.svelte';
|
||||||
|
import Share from '$lib/components/icons/Share.svelte';
|
||||||
|
import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
export let shareEnabled: boolean = false;
|
export let shareEnabled: boolean = false;
|
||||||
|
|
||||||
export let shareHandler: Function;
|
export let shareHandler: Function;
|
||||||
export let moveHandler: Function;
|
export let moveChatHandler: Function;
|
||||||
|
|
||||||
|
export let archiveChatHandler: Function;
|
||||||
|
|
||||||
// export let tagHandler: Function;
|
// export let tagHandler: Function;
|
||||||
|
|
||||||
export let chat;
|
export let chat;
|
||||||
|
|
@ -262,30 +270,6 @@
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !$temporaryChatEnabled && ($user?.role === 'admin' || ($user.permissions?.chat?.share ?? true))}
|
|
||||||
<DropdownMenu.Item
|
|
||||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
|
|
||||||
id="chat-share-button"
|
|
||||||
on:click={() => {
|
|
||||||
shareHandler();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="currentColor"
|
|
||||||
class="size-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div class="flex items-center">{$i18n.t('Share')}</div>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
|
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
|
||||||
id="chat-overview-button"
|
id="chat-overview-button"
|
||||||
|
|
@ -312,6 +296,61 @@
|
||||||
<div class="flex items-center">{$i18n.t('Artifacts')}</div>
|
<div class="flex items-center">{$i18n.t('Artifacts')}</div>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
|
|
||||||
|
<hr class="border-gray-100 dark:border-gray-800 my-1" />
|
||||||
|
|
||||||
|
{#if !$temporaryChatEnabled && ($user?.role === 'admin' || ($user.permissions?.chat?.share ?? true))}
|
||||||
|
<DropdownMenu.Item
|
||||||
|
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
|
||||||
|
id="chat-share-button"
|
||||||
|
on:click={() => {
|
||||||
|
shareHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Share />
|
||||||
|
<div class="flex items-center">{$i18n.t('Share')}</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if chat?.id}
|
||||||
|
<DropdownMenu.Sub>
|
||||||
|
<DropdownMenu.SubTrigger
|
||||||
|
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
|
||||||
|
>
|
||||||
|
<Folder />
|
||||||
|
|
||||||
|
<div class="flex items-center">{$i18n.t('Move')}</div>
|
||||||
|
</DropdownMenu.SubTrigger>
|
||||||
|
<DropdownMenu.SubContent
|
||||||
|
class="w-full rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg max-h-52 overflow-y-auto scrollbar-hidden"
|
||||||
|
transition={flyAndScale}
|
||||||
|
sideOffset={8}
|
||||||
|
>
|
||||||
|
{#each $folders.sort((a, b) => b.updated_at - a.updated_at) as folder}
|
||||||
|
<DropdownMenu.Item
|
||||||
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||||
|
on:click={() => {
|
||||||
|
moveChatHandler(chat?.id, folder?.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Folder />
|
||||||
|
|
||||||
|
<div class="flex items-center">{folder?.name ?? 'Folder'}</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{/each}
|
||||||
|
</DropdownMenu.SubContent>
|
||||||
|
</DropdownMenu.Sub>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<DropdownMenu.Item
|
||||||
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||||
|
on:click={() => {
|
||||||
|
archiveChatHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArchiveBox className="size-4" strokeWidth="2" />
|
||||||
|
<div class="flex items-center">{$i18n.t('Archive')}</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
|
||||||
<DropdownMenu.Sub>
|
<DropdownMenu.Sub>
|
||||||
<DropdownMenu.SubTrigger
|
<DropdownMenu.SubTrigger
|
||||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
|
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@
|
||||||
getChatList,
|
getChatList,
|
||||||
getChatListByTagName,
|
getChatListByTagName,
|
||||||
getPinnedChatList,
|
getPinnedChatList,
|
||||||
updateChatById
|
updateChatById,
|
||||||
|
updateChatFolderIdById
|
||||||
} from '$lib/apis/chats';
|
} from '$lib/apis/chats';
|
||||||
import {
|
import {
|
||||||
chatId,
|
chatId,
|
||||||
|
|
@ -136,6 +137,29 @@
|
||||||
dispatch('change');
|
dispatch('change');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const moveChatHandler = async (chatId, folderId) => {
|
||||||
|
if (chatId && folderId) {
|
||||||
|
const res = await updateChatFolderIdById(localStorage.token, chatId, folderId).catch(
|
||||||
|
(error) => {
|
||||||
|
toast.error(`${error}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
currentChatPage.set(1);
|
||||||
|
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||||
|
await pinnedChats.set(await getPinnedChatList(localStorage.token));
|
||||||
|
|
||||||
|
dispatch('change');
|
||||||
|
|
||||||
|
toast.success($i18n.t('Chat moved successfully'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.error($i18n.t('Failed to move chat'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let itemElement;
|
let itemElement;
|
||||||
|
|
||||||
let generating = false;
|
let generating = false;
|
||||||
|
|
@ -485,6 +509,7 @@
|
||||||
shareHandler={() => {
|
shareHandler={() => {
|
||||||
showShareChatModal = true;
|
showShareChatModal = true;
|
||||||
}}
|
}}
|
||||||
|
{moveChatHandler}
|
||||||
archiveChatHandler={() => {
|
archiveChatHandler={() => {
|
||||||
archiveChatHandler(id);
|
archiveChatHandler(id);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,17 @@
|
||||||
getChatPinnedStatusById,
|
getChatPinnedStatusById,
|
||||||
toggleChatPinnedStatusById
|
toggleChatPinnedStatusById
|
||||||
} from '$lib/apis/chats';
|
} from '$lib/apis/chats';
|
||||||
import { chats, settings, theme, user } from '$lib/stores';
|
import { chats, folders, settings, theme, user } from '$lib/stores';
|
||||||
import { createMessagesList } from '$lib/utils';
|
import { createMessagesList } from '$lib/utils';
|
||||||
import { downloadChatAsPDF } from '$lib/apis/utils';
|
import { downloadChatAsPDF } from '$lib/apis/utils';
|
||||||
import Download from '$lib/components/icons/ArrowDownTray.svelte';
|
import Download from '$lib/components/icons/ArrowDownTray.svelte';
|
||||||
|
import Folder from '$lib/components/icons/Folder.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
export let shareHandler: Function;
|
export let shareHandler: Function;
|
||||||
|
export let moveChatHandler: Function;
|
||||||
|
|
||||||
export let cloneChatHandler: Function;
|
export let cloneChatHandler: Function;
|
||||||
export let archiveChatHandler: Function;
|
export let archiveChatHandler: Function;
|
||||||
export let renameHandler: Function;
|
export let renameHandler: Function;
|
||||||
|
|
@ -253,6 +256,36 @@
|
||||||
{/if}
|
{/if}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
|
|
||||||
|
{#if chatId}
|
||||||
|
<DropdownMenu.Sub>
|
||||||
|
<DropdownMenu.SubTrigger
|
||||||
|
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
|
||||||
|
>
|
||||||
|
<Folder />
|
||||||
|
|
||||||
|
<div class="flex items-center">{$i18n.t('Move')}</div>
|
||||||
|
</DropdownMenu.SubTrigger>
|
||||||
|
<DropdownMenu.SubContent
|
||||||
|
class="w-full rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg max-h-52 overflow-y-auto scrollbar-hidden"
|
||||||
|
transition={flyAndScale}
|
||||||
|
sideOffset={8}
|
||||||
|
>
|
||||||
|
{#each $folders.sort((a, b) => b.updated_at - a.updated_at) as folder}
|
||||||
|
<DropdownMenu.Item
|
||||||
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||||
|
on:click={() => {
|
||||||
|
moveChatHandler(chatId, folder.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Folder />
|
||||||
|
|
||||||
|
<div class="flex items-center">{folder?.name ?? 'Folder'}</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{/each}
|
||||||
|
</DropdownMenu.SubContent>
|
||||||
|
</DropdownMenu.Sub>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue