enh: move chat menu

This commit is contained in:
Timothy Jaeryang Baek 2025-08-21 04:35:27 +04:00
parent f46837fd10
commit d720e027ad
5 changed files with 160 additions and 31 deletions

View file

@ -37,7 +37,8 @@
showArtifacts,
tools,
toolServers,
selectedFolder
selectedFolder,
pinnedChats
} from '$lib/stores';
import {
convertMessagesToHistory,
@ -55,8 +56,10 @@
getAllTags,
getChatById,
getChatList,
getPinnedChatList,
getTagsById,
updateChatById
updateChatById,
updateChatFolderIdById
} from '$lib/apis/chats';
import { generateOpenAIChatCompletion } from '$lib/apis/openai';
import { processWeb, processWebSearch, processYoutubeVideo } from '$lib/apis/retrieval';
@ -2118,6 +2121,27 @@
}
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>
<svelte:head>
@ -2192,6 +2216,8 @@
shareEnabled={!!history.currentId}
{initNewChat}
showBanners={!showCommands}
archiveChatHandler={() => {}}
{moveChatHandler}
onSaveTempChat={async () => {
try {
if (!history?.currentId || !Object.keys(history.messages).length) {

View file

@ -50,6 +50,8 @@
export let showBanners = true;
export let onSaveTempChat: () => {};
export let archiveChatHandler: (id: string) => void;
export let moveChatHandler: (id: string, folderId: string) => void;
let closedBannerIds = [];
@ -165,6 +167,10 @@
shareHandler={() => {
showShareChatModal = !showShareChatModal;
}}
archiveChatHandler={() => {
archiveChatHandler(chat.id);
}}
{moveChatHandler}
>
<button
class="flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition"

View file

@ -20,9 +20,11 @@
temporaryChatEnabled,
theme,
user,
settings
settings,
folders
} from '$lib/stores';
import { flyAndScale } from '$lib/utils/transitions';
import { getChatById } from '$lib/apis/chats';
import Dropdown from '$lib/components/common/Dropdown.svelte';
import Tags from '$lib/components/chat/Tags.svelte';
@ -30,13 +32,19 @@
import Clipboard from '$lib/components/icons/Clipboard.svelte';
import AdjustmentsHorizontal from '$lib/components/icons/AdjustmentsHorizontal.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');
export let shareEnabled: boolean = false;
export let shareHandler: Function;
export let moveHandler: Function;
export let moveChatHandler: Function;
export let archiveChatHandler: Function;
// export let tagHandler: Function;
export let chat;
@ -262,30 +270,6 @@
</DropdownMenu.Item>
{/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
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"
@ -312,6 +296,61 @@
<div class="flex items-center">{$i18n.t('Artifacts')}</div>
</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.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"

View file

@ -15,7 +15,8 @@
getChatList,
getChatListByTagName,
getPinnedChatList,
updateChatById
updateChatById,
updateChatFolderIdById
} from '$lib/apis/chats';
import {
chatId,
@ -136,6 +137,29 @@
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 generating = false;
@ -485,6 +509,7 @@
shareHandler={() => {
showShareChatModal = true;
}}
{moveChatHandler}
archiveChatHandler={() => {
archiveChatHandler(id);
}}

View file

@ -26,14 +26,17 @@
getChatPinnedStatusById,
toggleChatPinnedStatusById
} 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 { downloadChatAsPDF } from '$lib/apis/utils';
import Download from '$lib/components/icons/ArrowDownTray.svelte';
import Folder from '$lib/components/icons/Folder.svelte';
const i18n = getContext('i18n');
export let shareHandler: Function;
export let moveChatHandler: Function;
export let cloneChatHandler: Function;
export let archiveChatHandler: Function;
export let renameHandler: Function;
@ -253,6 +256,36 @@
{/if}
</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
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={() => {