Commit 83107c8e authored by Timothy J. Baek's avatar Timothy J. Baek
Browse files

feat: archive all

parent bf8c32f0
...@@ -148,7 +148,7 @@ async def get_archived_session_user_chat_list( ...@@ -148,7 +148,7 @@ async def get_archived_session_user_chat_list(
############################ ############################
@router.get("/archive/all", response_model=List[ChatTitleIdResponse]) @router.post("/archive/all", response_model=List[ChatTitleIdResponse])
async def archive_all_chats(user=Depends(get_current_user)): async def archive_all_chats(user=Depends(get_current_user)):
return Chats.archive_all_chats_by_user_id(user.id) return Chats.archive_all_chats_by_user_id(user.id)
......
...@@ -654,3 +654,35 @@ export const deleteAllChats = async (token: string) => { ...@@ -654,3 +654,35 @@ export const deleteAllChats = async (token: string) => {
return res; return res;
}; };
export const archiveAllChats = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/archive/all`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import { chats, user, config } from '$lib/stores'; import { chats, user, config } from '$lib/stores';
import { import {
archiveAllChats,
createNewChat, createNewChat,
deleteAllChats, deleteAllChats,
getAllChats, getAllChats,
...@@ -22,7 +23,10 @@ ...@@ -22,7 +23,10 @@
// Chats // Chats
let saveChatHistory = true; let saveChatHistory = true;
let importFiles; let importFiles;
let showArchiveConfirm = false;
let showDeleteConfirm = false; let showDeleteConfirm = false;
let chatImportInputElement: HTMLInputElement; let chatImportInputElement: HTMLInputElement;
$: if (importFiles) { $: if (importFiles) {
...@@ -68,6 +72,14 @@ ...@@ -68,6 +72,14 @@
saveAs(blob, `chat-export-${Date.now()}.json`); saveAs(blob, `chat-export-${Date.now()}.json`);
}; };
const archiveAllChatsHandler = async () => {
await goto('/');
await archiveAllChats(localStorage.token).catch((error) => {
toast.error(error);
});
await chats.set(await getChatList(localStorage.token));
};
const deleteAllChatsHandler = async () => { const deleteAllChatsHandler = async () => {
await goto('/'); await goto('/');
await deleteAllChats(localStorage.token).catch((error) => { await deleteAllChats(localStorage.token).catch((error) => {
...@@ -210,88 +222,177 @@ ...@@ -210,88 +222,177 @@
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
{#if showDeleteConfirm} <div class="flex flex-col">
<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition"> {#if showArchiveConfirm}
<div class="flex items-center space-x-3"> <div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
<svg <div class="flex items-center space-x-3">
xmlns="http://www.w3.org/2000/svg" <svg
viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"
fill="currentColor" viewBox="0 0 16 16"
class="w-4 h-4" fill="currentColor"
> class="w-4 h-4"
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" /> >
<path <path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
fill-rule="evenodd" <path
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM5.72 7.47a.75.75 0 0 1 1.06 0L8 8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 9.75l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 10.81l-1.22 1.22a.75.75 0 0 1-1.06-1.06l1.22-1.22-1.22-1.22a.75.75 0 0 1 0-1.06Z" fill-rule="evenodd"
clip-rule="evenodd" d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM5.72 7.47a.75.75 0 0 1 1.06 0L8 8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 9.75l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 10.81l-1.22 1.22a.75.75 0 0 1-1.06-1.06l1.22-1.22-1.22-1.22a.75.75 0 0 1 0-1.06Z"
/> clip-rule="evenodd"
</svg> />
<span>{$i18n.t('Are you sure?')}</span> </svg>
<span>{$i18n.t('Are you sure?')}</span>
</div>
<div class="flex space-x-1.5 items-center">
<button
class="hover:text-white transition"
on:click={() => {
archiveAllChatsHandler();
showArchiveConfirm = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clip-rule="evenodd"
/>
</svg>
</button>
<button
class="hover:text-white transition"
on:click={() => {
showArchiveConfirm = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
</div> </div>
{:else}
<button
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
showArchiveConfirm = true;
}}
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-4"
>
<path
d="M3.375 3C2.339 3 1.5 3.84 1.5 4.875v.75c0 1.036.84 1.875 1.875 1.875h17.25c1.035 0 1.875-.84 1.875-1.875v-.75C22.5 3.839 21.66 3 20.625 3H3.375Z"
/>
<path
fill-rule="evenodd"
d="m3.087 9 .54 9.176A3 3 0 0 0 6.62 21h10.757a3 3 0 0 0 2.995-2.824L20.913 9H3.087Zm6.163 3.75A.75.75 0 0 1 10 12h4a.75.75 0 0 1 0 1.5h-4a.75.75 0 0 1-.75-.75Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">{$i18n.t('Archive All Chats')}</div>
</button>
{/if}
<div class="flex space-x-1.5 items-center"> {#if showDeleteConfirm}
<button <div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
class="hover:text-white transition" <div class="flex items-center space-x-3">
on:click={() => {
deleteAllChatsHandler();
showDeleteConfirm = false;
}}
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" viewBox="0 0 16 16"
fill="currentColor" fill="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
<path <path
fill-rule="evenodd" fill-rule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM5.72 7.47a.75.75 0 0 1 1.06 0L8 8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 9.75l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 10.81l-1.22 1.22a.75.75 0 0 1-1.06-1.06l1.22-1.22-1.22-1.22a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd" clip-rule="evenodd"
/> />
</svg> </svg>
</button> <span>{$i18n.t('Are you sure?')}</span>
<button </div>
class="hover:text-white transition"
on:click={() => { <div class="flex space-x-1.5 items-center">
showDeleteConfirm = false; <button
}} class="hover:text-white transition"
> on:click={() => {
deleteAllChatsHandler();
showDeleteConfirm = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clip-rule="evenodd"
/>
</svg>
</button>
<button
class="hover:text-white transition"
on:click={() => {
showDeleteConfirm = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
</div>
{:else}
<button
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
showDeleteConfirm = true;
}}
>
<div class=" self-center mr-3">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" viewBox="0 0 16 16"
fill="currentColor" fill="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
<path <path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm7 7a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1 0-1.5h4.5A.75.75 0 0 1 11 9Z"
clip-rule="evenodd"
/> />
</svg> </svg>
</button> </div>
</div> <div class=" self-center text-sm font-medium">{$i18n.t('Delete All Chats')}</div>
</div> </button>
{:else} {/if}
<button </div>
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
showDeleteConfirm = true;
}}
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm7 7a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1 0-1.5h4.5A.75.75 0 0 1 11 9Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">{$i18n.t('Delete All Chats')}</div>
</button>
{/if}
</div> </div>
</div> </div>
...@@ -101,95 +101,99 @@ ...@@ -101,95 +101,99 @@
<hr class=" dark:border-gray-850 my-2" /> <hr class=" dark:border-gray-850 my-2" />
<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6"> <div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
{#if chats.length > 0} {#if chats.length > 0}
<div class="text-left text-sm w-full mb-3 max-h-[22rem] overflow-y-scroll"> <div>
<div class="relative overflow-x-auto"> <div class="text-left text-sm w-full mb-3 max-h-[22rem] overflow-y-scroll">
<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto"> <div class="relative overflow-x-auto">
<thead <table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-800" <thead
> class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-800"
<tr> >
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th> <tr>
<th scope="col" class="px-3 py-2 hidden md:flex"> {$i18n.t('Created At')} </th> <th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
<th scope="col" class="px-3 py-2 text-right" /> <th scope="col" class="px-3 py-2 hidden md:flex">
</tr> {$i18n.t('Created At')}
</thead> </th>
<tbody> <th scope="col" class="px-3 py-2 text-right" />
{#each chats.filter((c) => searchValue === '' || c.title </tr>
.toLowerCase() </thead>
.includes(searchValue.toLowerCase())) as chat, idx} <tbody>
<tr {#each chats.filter((c) => searchValue === '' || c.title
class="bg-transparent {idx !== chats.length - 1 && .toLowerCase()
'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs" .includes(searchValue.toLowerCase())) as chat, idx}
> <tr
<td class="px-3 py-1 w-2/3"> class="bg-transparent {idx !== chats.length - 1 &&
<a href="/c/{chat.id}" target="_blank"> 'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs"
<div class=" underline line-clamp-1"> >
{chat.title} <td class="px-3 py-1 w-2/3">
<a href="/c/{chat.id}" target="_blank">
<div class=" underline line-clamp-1">
{chat.title}
</div>
</a>
</td>
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
<div class="my-auto">
{dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
</div> </div>
</a> </td>
</td>
<td class="px-3 py-1 text-right">
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]"> <div class="flex justify-end w-full">
<div class="my-auto"> <Tooltip content="Unarchive Chat">
{dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))} <button
</div> class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
</td> on:click={async () => {
unarchiveChatHandler(chat.id);
<td class="px-3 py-1 text-right"> }}
<div class="flex justify-end w-full">
<Tooltip content="Unarchive Chat">
<button
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
on:click={async () => {
unarchiveChatHandler(chat.id);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-4"
> >
<path <svg
stroke-linecap="round" xmlns="http://www.w3.org/2000/svg"
stroke-linejoin="round" fill="none"
d="M9 8.25H7.5a2.25 2.25 0 0 0-2.25 2.25v9a2.25 2.25 0 0 0 2.25 2.25h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25H15m0-3-3-3m0 0-3 3m3-3V15" viewBox="0 0 24 24"
/> stroke-width="1.5"
</svg> stroke="currentColor"
</button> class="size-4"
</Tooltip> >
<path
<Tooltip content="Delete Chat"> stroke-linecap="round"
<button stroke-linejoin="round"
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl" d="M9 8.25H7.5a2.25 2.25 0 0 0-2.25 2.25v9a2.25 2.25 0 0 0 2.25 2.25h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25H15m0-3-3-3m0 0-3 3m3-3V15"
on:click={async () => { />
deleteChatHandler(chat.id); </svg>
}} </button>
> </Tooltip>
<svg
xmlns="http://www.w3.org/2000/svg" <Tooltip content="Delete Chat">
fill="none" <button
viewBox="0 0 24 24" class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
stroke-width="1.5" on:click={async () => {
stroke="currentColor" deleteChatHandler(chat.id);
class="w-4 h-4" }}
> >
<path <svg
stroke-linecap="round" xmlns="http://www.w3.org/2000/svg"
stroke-linejoin="round" fill="none"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" viewBox="0 0 24 24"
/> stroke-width="1.5"
</svg> stroke="currentColor"
</button> class="w-4 h-4"
</Tooltip> >
</div> <path
</td> stroke-linecap="round"
</tr> stroke-linejoin="round"
{/each} d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
</tbody> />
</table> </svg>
</button>
</Tooltip>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div> </div>
<div class="flex flex-wrap text-sm font-medium gap-1.5 mt-2 m-1"> <div class="flex flex-wrap text-sm font-medium gap-1.5 mt-2 m-1">
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment