mirror of
https://github.com/open-webui/open-webui
synced 2024-12-28 06:42:47 +00:00
enh: message edit
This commit is contained in:
parent
cdc75237b2
commit
83099a093d
@ -217,3 +217,120 @@ async def post_new_message(
|
|||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# UpdateMessageById
|
||||||
|
############################
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/{id}/messages/{message_id}/update", response_model=Optional[MessageModel]
|
||||||
|
)
|
||||||
|
async def update_message_by_id(
|
||||||
|
id: str, message_id: str, form_data: MessageForm, user=Depends(get_verified_user)
|
||||||
|
):
|
||||||
|
channel = Channels.get_channel_by_id(id)
|
||||||
|
if not channel:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
if not has_access(user.id, type="read", access_control=channel.access_control):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
)
|
||||||
|
|
||||||
|
message = Messages.get_message_by_id(message_id)
|
||||||
|
if not message:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
if message.channel_id != id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
message = Messages.update_message_by_id(message_id, form_data)
|
||||||
|
if message:
|
||||||
|
await sio.emit(
|
||||||
|
"channel-events",
|
||||||
|
{
|
||||||
|
"channel_id": channel.id,
|
||||||
|
"message_id": message.id,
|
||||||
|
"data": {
|
||||||
|
"type": "message:update",
|
||||||
|
"data": {
|
||||||
|
**message.model_dump(),
|
||||||
|
"user": UserNameResponse(**user.model_dump()).model_dump(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
to=f"channel:{channel.id}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return MessageModel(**message.model_dump())
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(e)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# DeleteMessageById
|
||||||
|
############################
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{id}/messages/{message_id}/delete", response_model=bool)
|
||||||
|
async def delete_message_by_id(
|
||||||
|
id: str, message_id: str, user=Depends(get_verified_user)
|
||||||
|
):
|
||||||
|
channel = Channels.get_channel_by_id(id)
|
||||||
|
if not channel:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
if not has_access(user.id, type="read", access_control=channel.access_control):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
)
|
||||||
|
|
||||||
|
message = Messages.get_message_by_id(message_id)
|
||||||
|
if not message:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
if message.channel_id != id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
Messages.delete_message_by_id(message_id)
|
||||||
|
await sio.emit(
|
||||||
|
"channel-events",
|
||||||
|
{
|
||||||
|
"channel_id": channel.id,
|
||||||
|
"message_id": message.id,
|
||||||
|
"data": {
|
||||||
|
"type": "message:delete",
|
||||||
|
"data": {
|
||||||
|
**message.model_dump(),
|
||||||
|
"user": UserNameResponse(**user.model_dump()).model_dump(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
to=f"channel:{channel.id}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(e)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
)
|
||||||
|
@ -18,10 +18,20 @@
|
|||||||
import Markdown from '$lib/components/chat/Messages/Markdown.svelte';
|
import Markdown from '$lib/components/chat/Messages/Markdown.svelte';
|
||||||
import ProfileImage from '$lib/components/chat/Messages/ProfileImage.svelte';
|
import ProfileImage from '$lib/components/chat/Messages/ProfileImage.svelte';
|
||||||
import Name from '$lib/components/chat/Messages/Name.svelte';
|
import Name from '$lib/components/chat/Messages/Name.svelte';
|
||||||
|
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||||
|
import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
|
||||||
|
import Pencil from '$lib/components/icons/Pencil.svelte';
|
||||||
|
|
||||||
export let message;
|
export let message;
|
||||||
export let showUserProfile = true;
|
export let showUserProfile = true;
|
||||||
|
|
||||||
|
export let onDelete: Function = () => {};
|
||||||
|
export let onEdit: Function = () => {};
|
||||||
|
|
||||||
|
let edit = false;
|
||||||
|
let editedContent = null;
|
||||||
|
let showDeleteConfirmDialog = false;
|
||||||
|
|
||||||
const formatDate = (inputDate) => {
|
const formatDate = (inputDate) => {
|
||||||
const date = dayjs(inputDate);
|
const date = dayjs(inputDate);
|
||||||
const now = dayjs();
|
const now = dayjs();
|
||||||
@ -36,14 +46,46 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:show={showDeleteConfirmDialog}
|
||||||
|
title={$i18n.t('Delete Message')}
|
||||||
|
message={$i18n.t('Are you sure you want to delete this message?')}
|
||||||
|
onConfirm={async () => {
|
||||||
|
await onDelete(message.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{#if message}
|
{#if message}
|
||||||
<div
|
<div
|
||||||
class="flex flex-col justify-between px-5 {showUserProfile
|
class="flex flex-col justify-between px-5 {showUserProfile
|
||||||
? 'pt-1.5 pb-0.5'
|
? 'pt-1.5 pb-0.5'
|
||||||
: ''} w-full {($settings?.widescreenMode ?? null)
|
: ''} w-full {($settings?.widescreenMode ?? null)
|
||||||
? 'max-w-full'
|
? 'max-w-full'
|
||||||
: 'max-w-5xl'} mx-auto group hover:bg-gray-500/5 transition"
|
: 'max-w-5xl'} mx-auto group hover:bg-gray-500/5 transition relative"
|
||||||
>
|
>
|
||||||
|
<div class=" absolute invisible group-hover:visible right-1 -top-2">
|
||||||
|
<div
|
||||||
|
class="flex gap-1 rounded-lg bg-white dark:bg-gray-850 shadow-md p-0.5 border border-gray-100 dark:border-gray-800"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="hover:bg-gray-100 dark:hover:bg-gray-800 transition rounded-lg p-1"
|
||||||
|
on:click={() => {
|
||||||
|
edit = true;
|
||||||
|
editedContent = message.content;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Pencil />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="hover:bg-gray-100 dark:hover:bg-gray-800 transition rounded-lg p-1"
|
||||||
|
on:click={() => (showDeleteConfirmDialog = true)}
|
||||||
|
>
|
||||||
|
<GarbageBin />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class=" flex w-full message-{message.id}"
|
class=" flex w-full message-{message.id}"
|
||||||
id="message-{message.id}"
|
id="message-{message.id}"
|
||||||
@ -88,9 +130,61 @@
|
|||||||
</Name>
|
</Name>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="markdown-prose">
|
{#if edit}
|
||||||
<Markdown id={message.id} content={message.content} />
|
<div class="py-1">
|
||||||
</div>
|
<textarea
|
||||||
|
id="message-edit-{message.id}"
|
||||||
|
class=" bg-transparent outline-none w-full resize-none"
|
||||||
|
bind:value={editedContent}
|
||||||
|
on:input={(e) => {
|
||||||
|
e.target.style.height = '';
|
||||||
|
e.target.style.height = `${e.target.scrollHeight}px`;
|
||||||
|
}}
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
document.getElementById('close-edit-message-button')?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCmdOrCtrlPressed = e.metaKey || e.ctrlKey;
|
||||||
|
const isEnterPressed = e.key === 'Enter';
|
||||||
|
|
||||||
|
if (isCmdOrCtrlPressed && isEnterPressed) {
|
||||||
|
document.getElementById('confirm-edit-message-button')?.click();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div class=" mt-2 mb-1 flex justify-end text-sm font-medium">
|
||||||
|
<div class="flex space-x-1.5">
|
||||||
|
<button
|
||||||
|
id="close-edit-message-button"
|
||||||
|
class="px-4 py-2 bg-white dark:bg-gray-900 hover:bg-gray-100 text-gray-800 dark:text-gray-100 transition rounded-3xl"
|
||||||
|
on:click={() => {
|
||||||
|
edit = false;
|
||||||
|
editedContent = null;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('Cancel')}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="confirm-edit-message-button"
|
||||||
|
class=" px-4 py-2 bg-gray-900 dark:bg-white hover:bg-gray-850 text-gray-100 dark:text-gray-800 transition rounded-3xl"
|
||||||
|
on:click={async () => {
|
||||||
|
onEdit(message.id, editedContent);
|
||||||
|
edit = false;
|
||||||
|
editedContent = null;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('Save')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="markdown-prose">
|
||||||
|
<Markdown id={message.id} content={message.content} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user