mirror of
https://github.com/open-webui/open-webui.git
synced 2026-11-14 09:21:22 +00:00
refac
This commit is contained in:
parent
4615d30a61
commit
75f8ae5584
9 changed files with 103 additions and 43 deletions
|
|
@ -87,7 +87,7 @@
|
|||
|
||||
let scrollEnd = true;
|
||||
let messagesContainerElement = null;
|
||||
let chatInputElement = null;
|
||||
let chatInputElement: { focus: () => void } | null = null;
|
||||
let threadChatInputElement = null;
|
||||
|
||||
let top = false;
|
||||
|
|
@ -148,6 +148,17 @@
|
|||
);
|
||||
};
|
||||
|
||||
let threadTrigger: HTMLButtonElement | null = null;
|
||||
const closeThread = async () => {
|
||||
const trigger = threadTrigger;
|
||||
threadTrigger = null;
|
||||
threadId = null;
|
||||
await tick();
|
||||
if (document.activeElement === document.body) {
|
||||
(trigger?.isConnected ? trigger : chatInputElement)?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const pinHandler = (
|
||||
messageId: string,
|
||||
pinned: boolean,
|
||||
|
|
@ -485,7 +496,8 @@
|
|||
await tick();
|
||||
chatInputElement?.focus();
|
||||
}}
|
||||
onThread={async (id) => {
|
||||
onThread={async (id: string, trigger: HTMLButtonElement) => {
|
||||
threadTrigger = trigger;
|
||||
threadId = id;
|
||||
await tick();
|
||||
threadChatInputElement?.focus();
|
||||
|
|
@ -541,22 +553,14 @@
|
|||
|
||||
{#if !largeScreen}
|
||||
{#if threadId !== null}
|
||||
<Drawer
|
||||
show={threadId !== null}
|
||||
ariaLabel={$i18n.t('Thread')}
|
||||
onClose={() => {
|
||||
threadId = null;
|
||||
}}
|
||||
>
|
||||
<Drawer show={threadId !== null} ariaLabel={$i18n.t('Thread')} onClose={closeThread}>
|
||||
<div class=" {threadId !== null ? ' h-screen w-full' : 'px-6 py-4'} h-full">
|
||||
<Thread
|
||||
{threadId}
|
||||
{channel}
|
||||
bind:chatInputElement={threadChatInputElement}
|
||||
onPin={pinHandler}
|
||||
onClose={() => {
|
||||
threadId = null;
|
||||
}}
|
||||
onClose={closeThread}
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
|
@ -576,9 +580,7 @@
|
|||
{channel}
|
||||
bind:chatInputElement={threadChatInputElement}
|
||||
onPin={pinHandler}
|
||||
onClose={() => {
|
||||
threadId = null;
|
||||
}}
|
||||
onClose={closeThread}
|
||||
/>
|
||||
</div>
|
||||
</ResizableSidePanel>
|
||||
|
|
|
|||
|
|
@ -218,9 +218,7 @@
|
|||
}
|
||||
);
|
||||
}}
|
||||
onThread={(id) => {
|
||||
onThread(id);
|
||||
}}
|
||||
{onThread}
|
||||
onReaction={(name) => {
|
||||
if (
|
||||
(message?.reactions ?? [])
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@
|
|||
};
|
||||
|
||||
let edit = false;
|
||||
let editButtonElement: HTMLButtonElement;
|
||||
let editedContent = null;
|
||||
let showDeleteConfirmDialog = false;
|
||||
$: renderedMessageId = message ? (id ? `${id}-${message.id}` : message.id) : null;
|
||||
|
|
@ -335,8 +336,8 @@
|
|||
<button
|
||||
aria-label={$i18n.t('Reply in Thread')}
|
||||
class="hover:bg-gray-100 dark:hover:bg-gray-800 transition rounded-lg p-1"
|
||||
on:click={() => {
|
||||
onThread(message.id);
|
||||
on:click={(event) => {
|
||||
onThread(message.id, event.currentTarget);
|
||||
}}
|
||||
>
|
||||
<ChatBubbleOvalEllipsis />
|
||||
|
|
@ -348,6 +349,7 @@
|
|||
{#if onEdit}
|
||||
<Tooltip content={$i18n.t('Edit')}>
|
||||
<button
|
||||
bind:this={editButtonElement}
|
||||
aria-label={$i18n.t('Edit')}
|
||||
id="edit-message-{renderedMessageId}"
|
||||
class="hover:bg-gray-100 dark:hover:bg-gray-800 transition rounded-lg p-1"
|
||||
|
|
@ -640,9 +642,11 @@
|
|||
<button
|
||||
id="close-edit-message-button"
|
||||
class="flex h-7 shrink-0 items-center justify-center gap-1.5 rounded-lg px-2.5 text-xs font-normal text-gray-600 transition hover:bg-gray-100 hover:text-gray-900 disabled:opacity-60 dark:text-gray-400 dark:hover:bg-gray-850 dark:hover:text-white"
|
||||
on:click={() => {
|
||||
on:click={async () => {
|
||||
edit = false;
|
||||
editedContent = null;
|
||||
await tick();
|
||||
editButtonElement?.focus();
|
||||
}}
|
||||
>
|
||||
{$i18n.t('Cancel')}
|
||||
|
|
@ -796,8 +800,8 @@
|
|||
<div class="flex items-center gap-1.5 -mt-0.5 mb-1.5">
|
||||
<button
|
||||
class="flex min-w-0 items-center text-[0.6875rem] py-1 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition"
|
||||
on:click={() => {
|
||||
onThread(message.id);
|
||||
on:click={(event) => {
|
||||
onThread(message.id, event.currentTarget);
|
||||
}}
|
||||
>
|
||||
<span class="shrink-0 font-medium mr-1">
|
||||
|
|
|
|||
|
|
@ -157,7 +157,10 @@
|
|||
class=" w-full group rounded-xl relative flex items-center justify-between hover:bg-gray-100 dark:hover:bg-gray-900 transition {buttonClassName}"
|
||||
>
|
||||
<h3 class="w-full">
|
||||
<button class="w-full py-1.5 pl-2 flex items-center gap-1.5 text-xs font-normal">
|
||||
<button
|
||||
id={id ? `${id}-button` : undefined}
|
||||
class="w-full py-1.5 pl-2 flex items-center gap-1.5 text-xs font-normal"
|
||||
>
|
||||
{#if chevron}
|
||||
<div class=" p-[0.0625rem]">
|
||||
{#if open}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@
|
|||
} from '$lib/apis/chats';
|
||||
import {
|
||||
chatId,
|
||||
folders,
|
||||
pinnedChats,
|
||||
chatTitle as _chatTitle,
|
||||
mobile,
|
||||
showSidebar,
|
||||
|
|
@ -245,6 +247,7 @@
|
|||
|
||||
let showShareChatModal = false;
|
||||
let confirmEdit = false;
|
||||
let menuButtonElement: HTMLButtonElement;
|
||||
|
||||
let chatTitle = '';
|
||||
|
||||
|
|
@ -295,6 +298,38 @@
|
|||
}
|
||||
};
|
||||
|
||||
const refreshAfterChange = async (folderId: string | null = null, removed = false) => {
|
||||
const restoreFocus =
|
||||
document.activeElement === document.body || itemElement?.contains(document.activeElement);
|
||||
const menus = Array.from(document.getElementsByName('sidebar-chat-menu'));
|
||||
const index = menus.indexOf(menuButtonElement);
|
||||
const neighbors = [...menus.slice(index + 1), ...menus.slice(0, index).reverse()].map(
|
||||
(button) => button.id
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
refreshSidebar(localStorage.token),
|
||||
getAllTags(localStorage.token).then((value) => tags.set(value))
|
||||
]);
|
||||
await tick();
|
||||
if (!restoreFocus || document.activeElement !== document.body) return;
|
||||
|
||||
const fallback = folderId
|
||||
? 'sidebar-folders-button'
|
||||
: $pinnedChats.some((chat) => chat.id === id)
|
||||
? 'sidebar-pinned-chats-button'
|
||||
: 'sidebar-chats-button';
|
||||
let target = removed
|
||||
? neighbors.map((id) => document.getElementById(id)).find(Boolean)
|
||||
: document.getElementById(`sidebar-chat-menu-${id}`);
|
||||
const folderList: { id: string; parent_id: string | null }[] = $folders;
|
||||
while (!target && folderId) {
|
||||
target = document.getElementById(`folder-${folderId}-expand-button`);
|
||||
folderId = folderList.find((folder) => folder.id === folderId)?.parent_id ?? null;
|
||||
}
|
||||
(target ?? document.getElementById(fallback))?.focus();
|
||||
};
|
||||
|
||||
let deleting = false;
|
||||
|
||||
const deleteChatHandler = async (id) => {
|
||||
|
|
@ -307,7 +342,6 @@
|
|||
});
|
||||
|
||||
if (res) {
|
||||
tags.set(await getAllTags(localStorage.token));
|
||||
if ($chatId === id) {
|
||||
await goto('/');
|
||||
|
||||
|
|
@ -315,7 +349,7 @@
|
|||
await tick();
|
||||
}
|
||||
|
||||
dispatch('change');
|
||||
await refreshAfterChange(null, true);
|
||||
}
|
||||
|
||||
deleting = false;
|
||||
|
|
@ -335,7 +369,7 @@
|
|||
chatId.set('');
|
||||
}
|
||||
|
||||
dispatch('change');
|
||||
await refreshAfterChange(null, true);
|
||||
toast.success($i18n.t('Chat archived.'));
|
||||
} catch (error) {
|
||||
console.error('Error archiving chat:', error);
|
||||
|
|
@ -355,9 +389,7 @@
|
|||
);
|
||||
|
||||
if (res) {
|
||||
await refreshChatList(localStorage.token, { refreshPinned: true });
|
||||
|
||||
dispatch('change');
|
||||
await refreshAfterChange(folderId);
|
||||
|
||||
toast.success($i18n.t('Chat moved successfully'));
|
||||
}
|
||||
|
|
@ -366,7 +398,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
let itemElement;
|
||||
let itemElement: HTMLDivElement;
|
||||
|
||||
let generating = false;
|
||||
|
||||
|
|
@ -450,7 +482,7 @@
|
|||
|
||||
let showDeleteConfirm = false;
|
||||
|
||||
const chatTitleInputKeydownHandler = (e) => {
|
||||
const chatTitleInputKeydownHandler = async (e: KeyboardEvent) => {
|
||||
// Let Enter and Escape finish IME composition without saving or cancelling the rename.
|
||||
if (e.isComposing || e.keyCode === 229) {
|
||||
return;
|
||||
|
|
@ -462,13 +494,14 @@
|
|||
if (chatTitle !== title) {
|
||||
editChatTitle(id, chatTitle);
|
||||
}
|
||||
}
|
||||
|
||||
confirmEdit = false;
|
||||
chatTitle = '';
|
||||
} else if (e.key === 'Escape') {
|
||||
if (e.key === 'Enter' || e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
confirmEdit = false;
|
||||
chatTitle = '';
|
||||
await tick();
|
||||
menuButtonElement?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -873,12 +906,13 @@
|
|||
menuOpen = false;
|
||||
dispatch('unselect');
|
||||
}}
|
||||
onPinChange={async () => {
|
||||
dispatch('change');
|
||||
}}
|
||||
onPinChange={(chat) => refreshAfterChange(chat.pinned ? null : chat.folder_id)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
bind:this={menuButtonElement}
|
||||
id="sidebar-chat-menu-{id}"
|
||||
name="sidebar-chat-menu"
|
||||
aria-label={$i18n.t('Chat Menu')}
|
||||
class="flex {($settings?.highContrastMode ?? false)
|
||||
? 'size-6'
|
||||
|
|
|
|||
|
|
@ -52,11 +52,14 @@
|
|||
let chat = null;
|
||||
let showFullMessages = false;
|
||||
|
||||
export let onPinChange: () => void = () => {};
|
||||
export let onPinChange: (chat: {
|
||||
pinned: boolean;
|
||||
folder_id: string | null;
|
||||
}) => void | Promise<void> = () => {};
|
||||
|
||||
const pinHandler = async () => {
|
||||
await toggleChatPinnedStatusById(localStorage.token, chatId);
|
||||
onPinChange();
|
||||
const chat = await toggleChatPinnedStatusById(localStorage.token, chatId);
|
||||
await onPinChange(chat);
|
||||
};
|
||||
|
||||
const checkPinned = async () => {
|
||||
|
|
|
|||
|
|
@ -785,6 +785,7 @@
|
|||
aria-label={open
|
||||
? $i18n.t('Collapse {{name}}', { name: folders[folderId]?.name })
|
||||
: $i18n.t('Expand {{name}}', { name: folders[folderId]?.name })}
|
||||
id="folder-{folderId}-expand-button"
|
||||
aria-expanded={open}
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@
|
|||
<button
|
||||
type="button"
|
||||
class="group flex flex-1 h-full items-center gap-1 text-left text-xs text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400 transition-colors duration-100 {buttonClassName}"
|
||||
id="{id}-button"
|
||||
aria-expanded={open}
|
||||
aria-controls="{id}-content"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -362,9 +362,12 @@
|
|||
});
|
||||
let noteCommentCount = 0;
|
||||
$: if (currentNoteComments) showNoteChat = true;
|
||||
const closeNotePanel = () => {
|
||||
const closeNotePanel = async () => {
|
||||
currentNoteComments = null;
|
||||
showNoteChat = false;
|
||||
|
||||
await tick();
|
||||
if (document.activeElement === document.body) noteChatButtonElement?.focus();
|
||||
};
|
||||
let noteChatId = null;
|
||||
let noteChatLoading = false;
|
||||
|
|
@ -390,6 +393,9 @@
|
|||
let ignoreBlur = false;
|
||||
let titleInputFocused = false;
|
||||
let titleGenerating = false;
|
||||
let titleInputElement: HTMLInputElement;
|
||||
let noteChatButtonElement: HTMLButtonElement;
|
||||
let pagesToggleElement: HTMLButtonElement;
|
||||
|
||||
let dragged = false;
|
||||
let loading = false;
|
||||
|
|
@ -579,6 +585,7 @@ ${content}
|
|||
|
||||
titleGenerating = false;
|
||||
await tick();
|
||||
if (document.activeElement === document.body) titleInputElement?.focus();
|
||||
changeDebounceHandler();
|
||||
};
|
||||
|
||||
|
|
@ -1150,6 +1157,7 @@ ${content}
|
|||
? 'ml-1'
|
||||
: ''}"
|
||||
type="text"
|
||||
bind:this={titleInputElement}
|
||||
bind:value={note.title}
|
||||
placeholder={titleGenerating ? $i18n.t('Generating...') : $i18n.t('Title')}
|
||||
aria-label={$i18n.t('Title')}
|
||||
|
|
@ -1249,6 +1257,7 @@ ${content}
|
|||
type="button"
|
||||
class="p-1 bg-transparent hover:bg-white/5 transition rounded-lg"
|
||||
aria-label={$i18n.t('Chat')}
|
||||
bind:this={noteChatButtonElement}
|
||||
on:click={openNoteChat}
|
||||
>
|
||||
<ChatBubbleOval className="size-4" strokeWidth="1.8" />
|
||||
|
|
@ -1423,6 +1432,7 @@ ${content}
|
|||
class="flex max-w-full items-center gap-1 bg-transparent text-xs text-gray-500 hover:text-gray-900 dark:hover:text-gray-100 transition"
|
||||
aria-expanded={showNoteTabs}
|
||||
aria-controls="note-tabs-panel"
|
||||
bind:this={pagesToggleElement}
|
||||
on:click={() => {
|
||||
showNoteTabs = !showNoteTabs;
|
||||
}}
|
||||
|
|
@ -1521,7 +1531,11 @@ ${content}
|
|||
floating
|
||||
>
|
||||
<NoteTabs
|
||||
onClose={() => (showNoteTabs = false)}
|
||||
onClose={async () => {
|
||||
showNoteTabs = false;
|
||||
await tick();
|
||||
pagesToggleElement?.focus();
|
||||
}}
|
||||
pages={note.data.pages.map((page) =>
|
||||
page.id === 'main' && page.inherit_note_title !== false
|
||||
? { ...page, title: note.title || $i18n.t('Untitled') }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue