Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
bcf78b4e
Commit
bcf78b4e
authored
Apr 27, 2024
by
Timothy J. Baek
Browse files
feat: show user chats in admin panel
parent
098ac187
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
302 additions
and
53 deletions
+302
-53
backend/apps/web/models/chats.py
backend/apps/web/models/chats.py
+9
-0
backend/apps/web/routers/chats.py
backend/apps/web/routers/chats.py
+11
-10
src/lib/apis/chats/index.ts
src/lib/apis/chats/index.ts
+31
-0
src/lib/components/admin/UserChatsModal.svelte
src/lib/components/admin/UserChatsModal.svelte
+141
-0
src/lib/components/chat/MessageInput.svelte
src/lib/components/chat/MessageInput.svelte
+8
-1
src/lib/components/icons/ChatBubble.svelte
src/lib/components/icons/ChatBubble.svelte
+19
-0
src/lib/components/icons/ChatBubbles.svelte
src/lib/components/icons/ChatBubbles.svelte
+19
-0
src/routes/(app)/admin/+page.svelte
src/routes/(app)/admin/+page.svelte
+64
-42
No files found.
backend/apps/web/models/chats.py
View file @
bcf78b4e
...
@@ -270,6 +270,15 @@ class ChatTable:
...
@@ -270,6 +270,15 @@ class ChatTable:
# .limit(limit).offset(skip)
# .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
:
def
delete_chat_by_id_and_user_id
(
self
,
id
:
str
,
user_id
:
str
)
->
bool
:
try
:
try
:
query
=
Chat
.
delete
().
where
((
Chat
.
id
==
id
)
&
(
Chat
.
user_id
==
user_id
))
query
=
Chat
.
delete
().
where
((
Chat
.
id
==
id
)
&
(
Chat
.
user_id
==
user_id
))
...
...
backend/apps/web/routers/chats.py
View file @
bcf78b4e
...
@@ -188,10 +188,11 @@ async def update_chat_by_id(
...
@@ -188,10 +188,11 @@ async def update_chat_by_id(
@
router
.
delete
(
"/{id}"
,
response_model
=
bool
)
@
router
.
delete
(
"/{id}"
,
response_model
=
bool
)
async
def
delete_chat_by_id
(
request
:
Request
,
id
:
str
,
user
=
Depends
(
get_current_user
)):
async
def
delete_chat_by_id
(
request
:
Request
,
id
:
str
,
user
=
Depends
(
get_current_user
)):
if
(
if
user
.
role
==
"admin"
:
user
.
role
==
"user"
result
=
Chats
.
delete_chat_by_id
(
id
)
and
not
request
.
app
.
state
.
USER_PERMISSIONS
[
"chat"
][
"deletion"
]
return
result
):
else
:
if
not
request
.
app
.
state
.
USER_PERMISSIONS
[
"chat"
][
"deletion"
]:
raise
HTTPException
(
raise
HTTPException
(
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
detail
=
ERROR_MESSAGES
.
ACCESS_PROHIBITED
,
detail
=
ERROR_MESSAGES
.
ACCESS_PROHIBITED
,
...
...
src/lib/apis/chats/index.ts
View file @
bcf78b4e
...
@@ -62,6 +62,37 @@ export const getChatList = async (token: string = '') => {
...
@@ -62,6 +62,37 @@ export const getChatList = async (token: string = '') => {
return
res
;
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
=
''
)
=>
{
export
const
getArchivedChatList
=
async
(
token
:
string
=
''
)
=>
{
let
error
=
null
;
let
error
=
null
;
...
...
src/lib/components/admin/UserChatsModal.svelte
0 → 100644
View file @
bcf78b4e
<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>
src/lib/components/chat/MessageInput.svelte
View file @
bcf78b4e
...
@@ -13,6 +13,7 @@
...
@@ -13,6 +13,7 @@
import Models from './MessageInput/Models.svelte';
import Models from './MessageInput/Models.svelte';
import { transcribeAudio } from '$lib/apis/audio';
import { transcribeAudio } from '$lib/apis/audio';
import Tooltip from '../common/Tooltip.svelte';
import Tooltip from '../common/Tooltip.svelte';
import Page from '../../../routes/(app)/+page.svelte';
const i18n = getContext('i18n');
const i18n = getContext('i18n');
...
@@ -692,6 +693,7 @@
...
@@ -692,6 +693,7 @@
e.preventDefault();
e.preventDefault();
}
}
if (prompt !== '' && e.keyCode == 13 && !e.shiftKey) {
if (prompt !== '' && e.keyCode == 13 && !e.shiftKey) {
// TODO: Only if screensize > xl
submitPrompt(prompt, user);
submitPrompt(prompt, user);
}
}
}}
}}
...
@@ -756,7 +758,11 @@
...
@@ -756,7 +758,11 @@
...document.getElementsByClassName('selected-command-option-button')
...document.getElementsByClassName('selected-command-option-button')
]?.at(-1);
]?.at(-1);
if (commandOptionButton) {
commandOptionButton?.click();
commandOptionButton?.click();
} else {
document.getElementById('send-message-button')?.click();
}
}
}
if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Tab') {
if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Tab') {
...
@@ -895,6 +901,7 @@
...
@@ -895,6 +901,7 @@
<Tooltip content={$i18n.t('Send message')}>
<Tooltip content={$i18n.t('Send message')}>
<button
<button
id="send-message-button"
class="{prompt !== ''
class="{prompt !== ''
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
? '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"
: 'text-white bg-gray-100 dark:text-gray-900 dark:bg-gray-800 disabled'} transition rounded-full p-1.5 self-center"
...
...
src/lib/components/icons/ChatBubble.svelte
0 → 100644
View file @
bcf78b4e
<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>
src/lib/components/icons/ChatBubbles.svelte
0 → 100644
View file @
bcf78b4e
<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>
src/routes/(app)/admin/+page.svelte
View file @
bcf78b4e
...
@@ -13,6 +13,9 @@
...
@@ -13,6 +13,9 @@
import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
import SettingsModal from '$lib/components/admin/SettingsModal.svelte';
import SettingsModal from '$lib/components/admin/SettingsModal.svelte';
import Pagination from '$lib/components/common/Pagination.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');
const i18n = getContext('i18n');
...
@@ -25,6 +28,8 @@
...
@@ -25,6 +28,8 @@
let page = 1;
let page = 1;
let showSettingsModal = false;
let showSettingsModal = false;
let showUserChatsModal = false;
let showEditUserModal = false;
let showEditUserModal = false;
const updateRoleHandler = async (id, role) => {
const updateRoleHandler = async (id, role) => {
...
@@ -84,6 +89,7 @@
...
@@ -84,6 +89,7 @@
/>
/>
{/key}
{/key}
<UserChatsModal bind:show={showUserChatsModal} user={selectedUser} />
<SettingsModal bind:show={showSettingsModal} />
<SettingsModal bind:show={showSettingsModal} />
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
...
@@ -220,6 +226,19 @@
...
@@ -220,6 +226,19 @@
<td class="px-3 py-2 text-right">
<td class="px-3 py-2 text-right">
<div class="flex justify-end w-full">
<div class="flex justify-end w-full">
<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;
}}
>
<ChatBubbles />
</button>
</Tooltip>
<Tooltip content="Edit User">
<button
<button
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
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 () => {
on:click={async () => {
...
@@ -242,7 +261,9 @@
...
@@ -242,7 +261,9 @@
/>
/>
</svg>
</svg>
</button>
</button>
</Tooltip>
<Tooltip content="Delete User">
<button
<button
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
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 () => {
on:click={async () => {
...
@@ -264,6 +285,7 @@
...
@@ -264,6 +285,7 @@
/>
/>
</svg>
</svg>
</button>
</button>
</Tooltip>
</div>
</div>
</td>
</td>
</tr>
</tr>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment