mirror of
https://github.com/open-webui/open-webui
synced 2024-12-28 06:42:47 +00:00
enh: update channel
This commit is contained in:
parent
e9194d9524
commit
7ad8918cd9
@ -78,6 +78,31 @@ async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
|
|||||||
return ChannelModel(**channel.model_dump())
|
return ChannelModel(**channel.model_dump())
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# UpdateChannelById
|
||||||
|
############################
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{id}/update", response_model=Optional[ChannelModel])
|
||||||
|
async def update_channel_by_id(
|
||||||
|
id: str, form_data: ChannelForm, user=Depends(get_admin_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
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
channel = Channels.update_channel_by_id(id, form_data)
|
||||||
|
return ChannelModel(**channel.model_dump())
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(e)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
# GetChannelMessages
|
# GetChannelMessages
|
||||||
############################
|
############################
|
||||||
|
@ -102,6 +102,38 @@ export const getChannelById = async (token: string = '', channel_id: string) =>
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const updateChannelById = async (token: string = '', channel_id: string, channel: ChannelForm) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/channels/${channel_id}/update`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
authorization: `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ ...channel })
|
||||||
|
})
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getChannelMessages = async (token: string = '', channel_id: string, page: number = 1) => {
|
export const getChannelMessages = async (token: string = '', channel_id: string, page: number = 1) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
{($settings?.widescreenMode ?? null) ? 'max-w-full' : 'max-w-5xl'} mx-auto"
|
{($settings?.widescreenMode ?? null) ? 'max-w-full' : 'max-w-5xl'} mx-auto"
|
||||||
>
|
>
|
||||||
{#if channel}
|
{#if channel}
|
||||||
<div class="flex flex-col py-1 gap-1.5 py-5">
|
<div class="flex flex-col gap-1.5 py-5">
|
||||||
<div class="text-2xl font-medium capitalize">{channel.name}</div>
|
<div class="text-2xl font-medium capitalize">{channel.name}</div>
|
||||||
|
|
||||||
<div class=" text-gray-500">
|
<div class=" text-gray-500">
|
||||||
@ -76,7 +76,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex justify-center py-1 text-xs items-center gap-2 py-5">
|
<div class="flex justify-center text-xs items-center gap-2 py-5">
|
||||||
<div class=" ">Start of the channel</div>
|
<div class=" ">Start of the channel</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
{#if message.created_at}
|
{#if message.created_at}
|
||||||
<span
|
<span
|
||||||
class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium capitalize ml-0.5 -mt-0.5"
|
class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium first-letter:capitalize ml-0.5 -mt-0.5"
|
||||||
>
|
>
|
||||||
{formatDate(message.created_at / 1000000)}
|
{formatDate(message.created_at / 1000000)}
|
||||||
</span>
|
</span>
|
||||||
|
@ -37,7 +37,6 @@
|
|||||||
|
|
||||||
const confirmHandler = async () => {
|
const confirmHandler = async () => {
|
||||||
show = false;
|
show = false;
|
||||||
|
|
||||||
await onConfirm();
|
await onConfirm();
|
||||||
dispatch('confirm', inputValue);
|
dispatch('confirm', inputValue);
|
||||||
};
|
};
|
||||||
@ -47,11 +46,15 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$: if (mounted) {
|
$: if (mounted) {
|
||||||
if (show) {
|
if (show && modalElement) {
|
||||||
|
document.body.appendChild(modalElement);
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
} else {
|
} else if (modalElement) {
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
document.body.removeChild(modalElement);
|
||||||
|
|
||||||
document.body.style.overflow = 'unset';
|
document.body.style.overflow = 'unset';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,7 +65,7 @@
|
|||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div
|
<div
|
||||||
bind:this={modalElement}
|
bind:this={modalElement}
|
||||||
class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] flex justify-center z-[99999] overflow-hidden overscroll-contain"
|
class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] flex justify-center z-[99999999] overflow-hidden overscroll-contain"
|
||||||
in:fade={{ duration: 10 }}
|
in:fade={{ duration: 10 }}
|
||||||
on:mousedown={() => {
|
on:mousedown={() => {
|
||||||
show = false;
|
show = false;
|
||||||
|
12
src/lib/components/icons/Cog6Solid.svelte
Normal file
12
src/lib/components/icons/Cog6Solid.svelte
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let className = 'w-4 h-4';
|
||||||
|
export let strokeWidth = '1.5';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class={className}>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M6.455 1.45A.5.5 0 0 1 6.952 1h2.096a.5.5 0 0 1 .497.45l.186 1.858a4.996 4.996 0 0 1 1.466.848l1.703-.769a.5.5 0 0 1 .639.206l1.047 1.814a.5.5 0 0 1-.14.656l-1.517 1.09a5.026 5.026 0 0 1 0 1.694l1.516 1.09a.5.5 0 0 1 .141.656l-1.047 1.814a.5.5 0 0 1-.639.206l-1.703-.768c-.433.36-.928.649-1.466.847l-.186 1.858a.5.5 0 0 1-.497.45H6.952a.5.5 0 0 1-.497-.45l-.186-1.858a4.993 4.993 0 0 1-1.466-.848l-1.703.769a.5.5 0 0 1-.639-.206l-1.047-1.814a.5.5 0 0 1 .14-.656l1.517-1.09a5.033 5.033 0 0 1 0-1.694l-1.516-1.09a.5.5 0 0 1-.141-.656L2.46 3.593a.5.5 0 0 1 .639-.206l1.703.769c.433-.36.928-.65 1.466-.848l.186-1.858Zm-.177 7.567-.022-.037a2 2 0 0 1 3.466-1.997l.022.037a2 2 0 0 1-3.466 1.997Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
@ -53,7 +53,7 @@
|
|||||||
import Tooltip from '../common/Tooltip.svelte';
|
import Tooltip from '../common/Tooltip.svelte';
|
||||||
import Folders from './Sidebar/Folders.svelte';
|
import Folders from './Sidebar/Folders.svelte';
|
||||||
import { getChannels, createNewChannel } from '$lib/apis/channels';
|
import { getChannels, createNewChannel } from '$lib/apis/channels';
|
||||||
import CreateChannelModal from './Sidebar/CreateChannelModal.svelte';
|
import ChannelModal from './Sidebar/ChannelModal.svelte';
|
||||||
import ChannelItem from './Sidebar/ChannelItem.svelte';
|
import ChannelItem from './Sidebar/ChannelItem.svelte';
|
||||||
import PencilSquare from '../icons/PencilSquare.svelte';
|
import PencilSquare from '../icons/PencilSquare.svelte';
|
||||||
|
|
||||||
@ -403,10 +403,21 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CreateChannelModal
|
<ChannelModal
|
||||||
bind:show={showCreateChannel}
|
bind:show={showCreateChannel}
|
||||||
onChange={async () => {
|
onSubmit={async ({ name, access_control }) => {
|
||||||
await initChannels();
|
const res = await createNewChannel(localStorage.token, {
|
||||||
|
name: name,
|
||||||
|
access_control: access_control
|
||||||
|
}).catch((error) => {
|
||||||
|
toast.error(error);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
await initChannels();
|
||||||
|
showCreateChannel = false;
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -642,7 +653,12 @@
|
|||||||
onAddLabel={$i18n.t('Create Channel')}
|
onAddLabel={$i18n.t('Create Channel')}
|
||||||
>
|
>
|
||||||
{#each $channels as channel}
|
{#each $channels as channel}
|
||||||
<ChannelItem id={channel.id} name={channel.name} />
|
<ChannelItem
|
||||||
|
{channel}
|
||||||
|
onUpdate={async () => {
|
||||||
|
await initChannels();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</Folder>
|
</Folder>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,33 +1,55 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { onMount, getContext, createEventDispatcher, tick, onDestroy } from 'svelte';
|
import { onMount, getContext, tick, onDestroy } from 'svelte';
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
import { mobile, showSidebar, user } from '$lib/stores';
|
import { mobile, showSidebar, user } from '$lib/stores';
|
||||||
import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte';
|
import { updateChannelById } from '$lib/apis/channels';
|
||||||
|
|
||||||
|
import Cog6 from '$lib/components/icons/Cog6.svelte';
|
||||||
|
import ChannelModal from './ChannelModal.svelte';
|
||||||
|
|
||||||
|
export let onUpdate: Function = () => {};
|
||||||
|
|
||||||
export let className = '';
|
export let className = '';
|
||||||
|
export let channel;
|
||||||
|
|
||||||
export let id;
|
let showEditChannelModal = false;
|
||||||
export let name;
|
|
||||||
|
|
||||||
let itemElement;
|
let itemElement;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<ChannelModal
|
||||||
|
bind:show={showEditChannelModal}
|
||||||
|
{channel}
|
||||||
|
edit={true}
|
||||||
|
onSubmit={async ({ name, access_control }) => {
|
||||||
|
const res = await updateChannelById(localStorage.token, channel.id, {
|
||||||
|
name,
|
||||||
|
access_control
|
||||||
|
}).catch((error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
toast.success('Channel updated successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={itemElement}
|
bind:this={itemElement}
|
||||||
class=" w-full {className} rounded-lg flex relative group hover:bg-gray-100 dark:hover:bg-gray-900 {$page
|
class=" w-full {className} rounded-lg flex relative group hover:bg-gray-100 dark:hover:bg-gray-900 {$page
|
||||||
.url.pathname === `/channels/${id}`
|
.url.pathname === `/channels/${channel.id}`
|
||||||
? 'bg-gray-100 dark:bg-gray-900'
|
? 'bg-gray-100 dark:bg-gray-900'
|
||||||
: ''} px-2.5 py-1"
|
: ''} px-2.5 py-1"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class=" w-full flex justify-between"
|
class=" w-full flex justify-between"
|
||||||
href="/channels/{id}"
|
href="/channels/{channel.id}"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
if ($mobile) {
|
if ($mobile) {
|
||||||
showSidebar.set(false);
|
showSidebar.set(false);
|
||||||
@ -50,7 +72,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<div class=" text-left self-center overflow-hidden w-full line-clamp-1">
|
<div class=" text-left self-center overflow-hidden w-full line-clamp-1">
|
||||||
{name}
|
{channel.name}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@ -60,10 +82,12 @@
|
|||||||
class="absolute z-10 right-2 invisible group-hover:visible self-center flex items-center dark:text-gray-300"
|
class="absolute z-10 right-2 invisible group-hover:visible self-center flex items-center dark:text-gray-300"
|
||||||
on:pointerup={(e) => {
|
on:pointerup={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
showEditChannelModal = true;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button class="p-0.5 dark:hover:bg-gray-850 rounded-lg touch-auto" on:click={(e) => {}}>
|
<button class="p-0.5 dark:hover:bg-gray-850 rounded-lg touch-auto" on:click={(e) => {}}>
|
||||||
<EllipsisHorizontal className="size-4" strokeWidth="2.5" />
|
<Cog6 className="size-3.5" />
|
||||||
</button>
|
</button>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, createEventDispatcher } from 'svelte';
|
import { getContext, createEventDispatcher, onMount } from 'svelte';
|
||||||
|
|
||||||
import { createNewChannel } from '$lib/apis/channels';
|
import { createNewChannel } from '$lib/apis/channels';
|
||||||
|
|
||||||
import Modal from '$lib/components/common/Modal.svelte';
|
import Modal from '$lib/components/common/Modal.svelte';
|
||||||
import AccessControl from '$lib/components/workspace/common/AccessControl.svelte';
|
import AccessControl from '$lib/components/workspace/common/AccessControl.svelte';
|
||||||
|
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||||
|
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
export let onChange: Function = () => {};
|
export let onSubmit: Function = () => {};
|
||||||
|
|
||||||
|
export let channel = null;
|
||||||
|
export let edit = false;
|
||||||
|
|
||||||
let name = '';
|
let name = '';
|
||||||
let accessControl = null;
|
let accessControl = null;
|
||||||
@ -22,25 +26,41 @@
|
|||||||
|
|
||||||
const submitHandler = async () => {
|
const submitHandler = async () => {
|
||||||
loading = true;
|
loading = true;
|
||||||
const res = await createNewChannel(localStorage.token, {
|
await onSubmit({
|
||||||
name: name.replace(/\s/g, '-'),
|
name: name.replace(/\s/g, '-'),
|
||||||
access_control: accessControl
|
access_control: accessControl
|
||||||
}).catch((error) => {
|
|
||||||
toast.error(error);
|
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onChange();
|
|
||||||
show = false;
|
show = false;
|
||||||
|
|
||||||
loading = false;
|
loading = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
name = channel.name;
|
||||||
|
accessControl = channel.access_control;
|
||||||
|
};
|
||||||
|
|
||||||
|
$: if (channel) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
let showDeleteConfirmDialog = false;
|
||||||
|
|
||||||
|
const deleteHandler = async () => {
|
||||||
|
showDeleteConfirmDialog = false;
|
||||||
|
show = false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal size="sm" bind:show>
|
<Modal size="sm" bind:show>
|
||||||
<div>
|
<div>
|
||||||
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
|
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
|
||||||
<div class=" text-lg font-medium self-center">{$i18n.t('Create Channel')}</div>
|
<div class=" text-lg font-medium self-center">
|
||||||
|
{#if edit}
|
||||||
|
{$i18n.t('Edit Channel')}
|
||||||
|
{:else}
|
||||||
|
{$i18n.t('Create Channel')}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
class="self-center"
|
class="self-center"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
@ -93,6 +113,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end pt-3 text-sm font-medium gap-1.5">
|
<div class="flex justify-end pt-3 text-sm font-medium gap-1.5">
|
||||||
|
{#if edit}
|
||||||
|
<button
|
||||||
|
class="px-3.5 py-1.5 text-sm font-medium dark:bg-black dark:hover:bg-black/90 dark:text-white bg-white text-black hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center"
|
||||||
|
type="button"
|
||||||
|
on:click={() => {
|
||||||
|
showDeleteConfirmDialog = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('Delete')}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-950 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center {loading
|
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-950 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center {loading
|
||||||
? ' cursor-not-allowed'
|
? ' cursor-not-allowed'
|
||||||
@ -100,7 +132,11 @@
|
|||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{$i18n.t('Create')}
|
{#if edit}
|
||||||
|
{$i18n.t('Update')}
|
||||||
|
{:else}
|
||||||
|
{$i18n.t('Create')}
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="ml-2 self-center">
|
<div class="ml-2 self-center">
|
||||||
@ -136,3 +172,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<DeleteConfirmDialog
|
||||||
|
bind:show={showDeleteConfirmDialog}
|
||||||
|
message={$i18n.t('Are you sure you want to delete this channel?')}
|
||||||
|
confirmLabel={$i18n.t('Delete')}
|
||||||
|
on:confirm={() => {
|
||||||
|
deleteHandler();
|
||||||
|
}}
|
||||||
|
/>
|
Loading…
Reference in New Issue
Block a user