diff --git a/backend/apps/web/models/chats.py b/backend/apps/web/models/chats.py index c83f26a9c..891151b94 100644 --- a/backend/apps/web/models/chats.py +++ b/backend/apps/web/models/chats.py @@ -270,6 +270,15 @@ class ChatTable: # .limit(limit).offset(skip) ] + def delete_chat_by_id(self, id: str) -> bool: + try: + query = Chat.delete().where((Chat.id == id)) + query.execute() # Remove the rows, return number of rows removed. + + return True and self.delete_shared_chat_by_chat_id(id) + except: + return False + def delete_chat_by_id_and_user_id(self, id: str, user_id: str) -> bool: try: query = Chat.delete().where((Chat.id == id) & (Chat.user_id == user_id)) diff --git a/backend/apps/web/routers/chats.py b/backend/apps/web/routers/chats.py index 4d03740cb..73b15e04a 100644 --- a/backend/apps/web/routers/chats.py +++ b/backend/apps/web/routers/chats.py @@ -188,17 +188,18 @@ async def update_chat_by_id( @router.delete("/{id}", response_model=bool) async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_user)): - if ( - user.role == "user" - and not request.app.state.USER_PERMISSIONS["chat"]["deletion"] - ): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=ERROR_MESSAGES.ACCESS_PROHIBITED, - ) + if user.role == "admin": + result = Chats.delete_chat_by_id(id) + return result + else: + if not request.app.state.USER_PERMISSIONS["chat"]["deletion"]: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) - result = Chats.delete_chat_by_id_and_user_id(id, user.id) - return result + result = Chats.delete_chat_by_id_and_user_id(id, user.id) + return result ############################ diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 5a9071bbc..7b76b11fa 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -62,6 +62,37 @@ export const getChatList = async (token: string = '') => { return res; }; +export const getChatListByUserId = async (token: string = '', userId: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/list/user/${userId}`, { + method: 'GET', + 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; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const getArchivedChatList = async (token: string = '') => { let error = null; diff --git a/src/lib/components/admin/UserChatsModal.svelte b/src/lib/components/admin/UserChatsModal.svelte new file mode 100644 index 000000000..4cf6425ed --- /dev/null +++ b/src/lib/components/admin/UserChatsModal.svelte @@ -0,0 +1,141 @@ + + + + + + + {$i18n.t("{{user}}'s Chats", { user: user.name })} + + { + show = false; + }} + > + + + + + + + + + + {#if chats.length > 0} + + + + + + {$i18n.t('Name')} + {$i18n.t('Created At')} + + + + + {#each chats as chat, idx} + + + + + {chat.title} + + + + + + + {dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))} + + + + + + + { + deleteChatHandler(chat.id); + }} + > + + + + + + + + + {/each} + + + + + + {:else} + You have no archived conversations. + {/if} + + + + diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index 2793a8d4b..ba907f780 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -13,6 +13,7 @@ import Models from './MessageInput/Models.svelte'; import { transcribeAudio } from '$lib/apis/audio'; import Tooltip from '../common/Tooltip.svelte'; + import Page from '../../../routes/(app)/+page.svelte'; const i18n = getContext('i18n'); @@ -692,6 +693,7 @@ e.preventDefault(); } if (prompt !== '' && e.keyCode == 13 && !e.shiftKey) { + // TODO: Only if screensize > xl submitPrompt(prompt, user); } }} @@ -756,7 +758,11 @@ ...document.getElementsByClassName('selected-command-option-button') ]?.at(-1); - commandOptionButton?.click(); + if (commandOptionButton) { + commandOptionButton?.click(); + } else { + document.getElementById('send-message-button')?.click(); + } } if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Tab') { @@ -895,6 +901,7 @@ + export let className = 'size-4'; + export let strokeWidth = '1.5'; + + + + + diff --git a/src/lib/components/icons/ChatBubbles.svelte b/src/lib/components/icons/ChatBubbles.svelte new file mode 100644 index 000000000..1651746fd --- /dev/null +++ b/src/lib/components/icons/ChatBubbles.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/src/routes/(app)/admin/+page.svelte b/src/routes/(app)/admin/+page.svelte index a3493cb66..6211bb6de 100644 --- a/src/routes/(app)/admin/+page.svelte +++ b/src/routes/(app)/admin/+page.svelte @@ -13,6 +13,9 @@ import EditUserModal from '$lib/components/admin/EditUserModal.svelte'; import SettingsModal from '$lib/components/admin/SettingsModal.svelte'; import Pagination from '$lib/components/common/Pagination.svelte'; + import ChatBubbles from '$lib/components/icons/ChatBubbles.svelte'; + import Tooltip from '$lib/components/common/Tooltip.svelte'; + import UserChatsModal from '$lib/components/admin/UserChatsModal.svelte'; const i18n = getContext('i18n'); @@ -25,6 +28,8 @@ let page = 1; let showSettingsModal = false; + + let showUserChatsModal = false; let showEditUserModal = false; const updateRoleHandler = async (id, role) => { @@ -84,6 +89,7 @@ /> {/key} + @@ -220,50 +226,66 @@ - { - showEditUserModal = !showEditUserModal; - selectedUser = user; - }} - > - + { + showUserChatsModal = !showUserChatsModal; + selectedUser = user; + }} > - - - + + + - { - deleteUserHandler(user.id); - }} - > - + { + showEditUserModal = !showEditUserModal; + selectedUser = user; + }} > - - - + + + + + + + + { + deleteUserHandler(user.id); + }} + > + + + + +