forked from open-webui/open-webui
		
	feat: show user chats in admin panel
This commit is contained in:
		
							parent
							
								
									098ac18762
								
							
						
					
					
						commit
						bcf78b4efa
					
				
					 8 changed files with 301 additions and 52 deletions
				
			
		|  | @ -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)) | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
| 
 | ||||
| ############################ | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										141
									
								
								src/lib/components/admin/UserChatsModal.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/lib/components/admin/UserChatsModal.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,141 @@ | |||
| <script lang="ts"> | ||||
| 	import { toast } from 'svelte-sonner'; | ||||
| 	import dayjs from 'dayjs'; | ||||
| 	import { getContext, createEventDispatcher } from 'svelte'; | ||||
| 
 | ||||
| 	const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
| 	import Modal from '$lib/components/common/Modal.svelte'; | ||||
| 	import { getChatListByUserId, deleteChatById, getArchivedChatList } from '$lib/apis/chats'; | ||||
| 	import Tooltip from '$lib/components/common/Tooltip.svelte'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	export let show = false; | ||||
| 	export let user; | ||||
| 
 | ||||
| 	let chats = []; | ||||
| 
 | ||||
| 	const deleteChatHandler = async (chatId) => { | ||||
| 		const res = await deleteChatById(localStorage.token, chatId).catch((error) => { | ||||
| 			toast.error(error); | ||||
| 		}); | ||||
| 
 | ||||
| 		chats = await getChatListByUserId(localStorage.token, user.id); | ||||
| 	}; | ||||
| 
 | ||||
| 	$: if (show) { | ||||
| 		(async () => { | ||||
| 			if (user.id) { | ||||
| 				chats = await getChatListByUserId(localStorage.token, user.id); | ||||
| 			} | ||||
| 		})(); | ||||
| 	} | ||||
| </script> | ||||
| 
 | ||||
| <Modal size="lg" bind:show> | ||||
| 	<div> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4"> | ||||
| 			<div class=" text-lg font-medium self-center capitalize"> | ||||
| 				{$i18n.t("{{user}}'s Chats", { user: user.name })} | ||||
| 			</div> | ||||
| 			<button | ||||
| 				class="self-center" | ||||
| 				on:click={() => { | ||||
| 					show = false; | ||||
| 				}} | ||||
| 			> | ||||
| 				<svg | ||||
| 					xmlns="http://www.w3.org/2000/svg" | ||||
| 					viewBox="0 0 20 20" | ||||
| 					fill="currentColor" | ||||
| 					class="w-5 h-5" | ||||
| 				> | ||||
| 					<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> | ||||
| 		<hr class=" dark:border-gray-850" /> | ||||
| 
 | ||||
| 		<div class="flex flex-col md:flex-row w-full px-5 py-4 md:space-x-4 dark:text-gray-200"> | ||||
| 			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6"> | ||||
| 				{#if chats.length > 0} | ||||
| 					<div class="text-left text-sm w-full mb-4 max-h-[22rem] overflow-y-scroll"> | ||||
| 						<div class="relative overflow-x-auto"> | ||||
| 							<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto"> | ||||
| 								<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> | ||||
| 										<th scope="col" class="px-3 py-2 hidden md:flex"> {$i18n.t('Created At')} </th> | ||||
| 										<th scope="col" class="px-3 py-2 text-right" /> | ||||
| 									</tr> | ||||
| 								</thead> | ||||
| 								<tbody> | ||||
| 									{#each chats as chat, idx} | ||||
| 										<tr | ||||
| 											class="bg-transparent {idx !== chats.length - 1 && | ||||
| 												'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs" | ||||
| 										> | ||||
| 											<td class="px-3 py-1 w-2/3"> | ||||
| 												<a href="/s/{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> | ||||
| 											</td> | ||||
| 
 | ||||
| 											<td class="px-3 py-1 text-right"> | ||||
| 												<div class="flex justify-end w-full"> | ||||
| 													<Tooltip content="Delete 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 () => { | ||||
| 																deleteChatHandler(chat.id); | ||||
| 															}} | ||||
| 														> | ||||
| 															<svg | ||||
| 																xmlns="http://www.w3.org/2000/svg" | ||||
| 																fill="none" | ||||
| 																viewBox="0 0 24 24" | ||||
| 																stroke-width="1.5" | ||||
| 																stroke="currentColor" | ||||
| 																class="w-4 h-4" | ||||
| 															> | ||||
| 																<path | ||||
| 																	stroke-linecap="round" | ||||
| 																	stroke-linejoin="round" | ||||
| 																	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" | ||||
| 																/> | ||||
| 															</svg> | ||||
| 														</button> | ||||
| 													</Tooltip> | ||||
| 												</div> | ||||
| 											</td> | ||||
| 										</tr> | ||||
| 									{/each} | ||||
| 								</tbody> | ||||
| 							</table> | ||||
| 						</div> | ||||
| 						<!-- {#each chats as chat} | ||||
| 							<div> | ||||
| 								{JSON.stringify(chat)} | ||||
| 							</div> | ||||
| 						{/each} --> | ||||
| 					</div> | ||||
| 				{:else} | ||||
| 					<div class="text-left text-sm w-full mb-8">You have no archived conversations.</div> | ||||
| 				{/if} | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </Modal> | ||||
|  | @ -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 @@ | |||
| 
 | ||||
| 								<Tooltip content={$i18n.t('Send message')}> | ||||
| 									<button | ||||
| 										id="send-message-button" | ||||
| 										class="{prompt !== '' | ||||
| 											? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 ' | ||||
| 											: 'text-white bg-gray-100 dark:text-gray-900 dark:bg-gray-800 disabled'} transition rounded-full p-1.5 self-center" | ||||
|  |  | |||
							
								
								
									
										19
									
								
								src/lib/components/icons/ChatBubble.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/lib/components/icons/ChatBubble.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| <script lang="ts"> | ||||
| 	export let className = 'size-4'; | ||||
| 	export let strokeWidth = '1.5'; | ||||
| </script> | ||||
| 
 | ||||
| <svg | ||||
| 	xmlns="http://www.w3.org/2000/svg" | ||||
| 	fill="none" | ||||
| 	viewBox="0 0 24 24" | ||||
| 	stroke-width={strokeWidth} | ||||
| 	stroke="currentColor" | ||||
| 	class={className} | ||||
| > | ||||
| 	<path | ||||
| 		stroke-linecap="round" | ||||
| 		stroke-linejoin="round" | ||||
| 		d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z" | ||||
| 	/> | ||||
| </svg> | ||||
							
								
								
									
										19
									
								
								src/lib/components/icons/ChatBubbles.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/lib/components/icons/ChatBubbles.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| <script lang="ts"> | ||||
| 	export let className = 'size-4'; | ||||
| 	export let strokeWidth = '1.5'; | ||||
| </script> | ||||
| 
 | ||||
| <svg | ||||
| 	xmlns="http://www.w3.org/2000/svg" | ||||
| 	fill="none" | ||||
| 	viewBox="0 0 24 24" | ||||
| 	stroke-width={strokeWidth} | ||||
| 	stroke="currentColor" | ||||
| 	class={className} | ||||
| > | ||||
| 	<path | ||||
| 		stroke-linecap="round" | ||||
| 		stroke-linejoin="round" | ||||
| 		d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 0 1-.825-.242m9.345-8.334a2.126 2.126 0 0 0-.476-.095 48.64 48.64 0 0 0-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0 0 11.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155" | ||||
| 	/> | ||||
| </svg> | ||||
|  | @ -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} | ||||
| 
 | ||||
| <UserChatsModal bind:show={showUserChatsModal} user={selectedUser} /> | ||||
| <SettingsModal bind:show={showSettingsModal} /> | ||||
| 
 | ||||
| <div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white"> | ||||
|  | @ -220,50 +226,66 @@ | |||
| 
 | ||||
| 												<td class="px-3 py-2 text-right"> | ||||
| 													<div class="flex justify-end w-full"> | ||||
| 														<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 () => { | ||||
| 																showEditUserModal = !showEditUserModal; | ||||
| 																selectedUser = user; | ||||
| 															}} | ||||
| 														> | ||||
| 															<svg | ||||
| 																xmlns="http://www.w3.org/2000/svg" | ||||
| 																fill="none" | ||||
| 																viewBox="0 0 24 24" | ||||
| 																stroke-width="1.5" | ||||
| 																stroke="currentColor" | ||||
| 																class="w-4 h-4" | ||||
| 														<Tooltip content="Chats"> | ||||
| 															<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 () => { | ||||
| 																	showUserChatsModal = !showUserChatsModal; | ||||
| 																	selectedUser = user; | ||||
| 																}} | ||||
| 															> | ||||
| 																<path | ||||
| 																	stroke-linecap="round" | ||||
| 																	stroke-linejoin="round" | ||||
| 																	d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125" | ||||
| 																/> | ||||
| 															</svg> | ||||
| 														</button> | ||||
| 																<ChatBubbles /> | ||||
| 															</button> | ||||
| 														</Tooltip> | ||||
| 
 | ||||
| 														<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 () => { | ||||
| 																deleteUserHandler(user.id); | ||||
| 															}} | ||||
| 														> | ||||
| 															<svg | ||||
| 																xmlns="http://www.w3.org/2000/svg" | ||||
| 																fill="none" | ||||
| 																viewBox="0 0 24 24" | ||||
| 																stroke-width="1.5" | ||||
| 																stroke="currentColor" | ||||
| 																class="w-4 h-4" | ||||
| 														<Tooltip content="Edit User"> | ||||
| 															<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 () => { | ||||
| 																	showEditUserModal = !showEditUserModal; | ||||
| 																	selectedUser = user; | ||||
| 																}} | ||||
| 															> | ||||
| 																<path | ||||
| 																	stroke-linecap="round" | ||||
| 																	stroke-linejoin="round" | ||||
| 																	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" | ||||
| 																/> | ||||
| 															</svg> | ||||
| 														</button> | ||||
| 																<svg | ||||
| 																	xmlns="http://www.w3.org/2000/svg" | ||||
| 																	fill="none" | ||||
| 																	viewBox="0 0 24 24" | ||||
| 																	stroke-width="1.5" | ||||
| 																	stroke="currentColor" | ||||
| 																	class="w-4 h-4" | ||||
| 																> | ||||
| 																	<path | ||||
| 																		stroke-linecap="round" | ||||
| 																		stroke-linejoin="round" | ||||
| 																		d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125" | ||||
| 																	/> | ||||
| 																</svg> | ||||
| 															</button> | ||||
| 														</Tooltip> | ||||
| 
 | ||||
| 														<Tooltip content="Delete User"> | ||||
| 															<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 () => { | ||||
| 																	deleteUserHandler(user.id); | ||||
| 																}} | ||||
| 															> | ||||
| 																<svg | ||||
| 																	xmlns="http://www.w3.org/2000/svg" | ||||
| 																	fill="none" | ||||
| 																	viewBox="0 0 24 24" | ||||
| 																	stroke-width="1.5" | ||||
| 																	stroke="currentColor" | ||||
| 																	class="w-4 h-4" | ||||
| 																> | ||||
| 																	<path | ||||
| 																		stroke-linecap="round" | ||||
| 																		stroke-linejoin="round" | ||||
| 																		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" | ||||
| 																	/> | ||||
| 																</svg> | ||||
| 															</button> | ||||
| 														</Tooltip> | ||||
| 													</div> | ||||
| 												</td> | ||||
| 											</tr> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy J. Baek
						Timothy J. Baek