mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-12 12:25:20 +00:00
enh: search chat preview
This commit is contained in:
parent
c94ce71ca3
commit
71ee33bf82
3 changed files with 219 additions and 66 deletions
|
|
@ -26,8 +26,14 @@
|
||||||
return 'w-[30rem]';
|
return 'w-[30rem]';
|
||||||
} else if (size === 'md') {
|
} else if (size === 'md') {
|
||||||
return 'w-[42rem]';
|
return 'w-[42rem]';
|
||||||
} else {
|
} else if (size === 'lg') {
|
||||||
return 'w-[56rem]';
|
return 'w-[56rem]';
|
||||||
|
} else if (size === 'xl') {
|
||||||
|
return 'w-[70rem]';
|
||||||
|
} else if (size === '2xl') {
|
||||||
|
return 'w-[84rem]';
|
||||||
|
} else if (size === '3xl') {
|
||||||
|
return 'w-[100rem]';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { getContext, onMount } from 'svelte';
|
import { getContext, onDestroy, onMount, tick } from 'svelte';
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
import Modal from '$lib/components/common/Modal.svelte';
|
import Modal from '$lib/components/common/Modal.svelte';
|
||||||
import SearchInput from './Sidebar/SearchInput.svelte';
|
import SearchInput from './Sidebar/SearchInput.svelte';
|
||||||
import { getChatList, getChatListBySearchText } from '$lib/apis/chats';
|
import { getChatById, getChatList, getChatListBySearchText } from '$lib/apis/chats';
|
||||||
import Spinner from '../common/Spinner.svelte';
|
import Spinner from '../common/Spinner.svelte';
|
||||||
|
|
||||||
import dayjs from '$lib/dayjs';
|
import dayjs from '$lib/dayjs';
|
||||||
import calendar from 'dayjs/plugin/calendar';
|
import calendar from 'dayjs/plugin/calendar';
|
||||||
import Loader from '../common/Loader.svelte';
|
import Loader from '../common/Loader.svelte';
|
||||||
|
import { createMessagesList } from '$lib/utils';
|
||||||
|
import { user } from '$lib/stores';
|
||||||
|
import Messages from '../chat/Messages.svelte';
|
||||||
dayjs.extend(calendar);
|
dayjs.extend(calendar);
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
|
|
@ -28,6 +31,58 @@
|
||||||
|
|
||||||
let selectedIdx = 0;
|
let selectedIdx = 0;
|
||||||
|
|
||||||
|
let selectedChat = null;
|
||||||
|
|
||||||
|
let selectedModels = [''];
|
||||||
|
let history = null;
|
||||||
|
let messages = null;
|
||||||
|
|
||||||
|
$: loadChatPreview(selectedIdx);
|
||||||
|
|
||||||
|
const loadChatPreview = async (selectedIdx) => {
|
||||||
|
if (!chatList || chatList.length === 0) {
|
||||||
|
selectedChat = null;
|
||||||
|
messages = null;
|
||||||
|
history = null;
|
||||||
|
selectedModels = [''];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatId = chatList[selectedIdx].id;
|
||||||
|
|
||||||
|
const chat = await getChatById(localStorage.token, chatId).catch(async (error) => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (chat) {
|
||||||
|
if (chat?.chat?.history) {
|
||||||
|
selectedModels =
|
||||||
|
(chat?.chat?.models ?? undefined) !== undefined
|
||||||
|
? chat?.chat?.models
|
||||||
|
: [chat?.chat?.models ?? ''];
|
||||||
|
|
||||||
|
history = chat?.chat?.history;
|
||||||
|
messages = createMessagesList(chat?.chat?.history, chat?.chat?.history?.currentId);
|
||||||
|
|
||||||
|
// scroll to the bottom of the messages container
|
||||||
|
await tick();
|
||||||
|
const messagesContainerElement = document.getElementById('chat-preview');
|
||||||
|
if (messagesContainerElement) {
|
||||||
|
messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
messages = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.error($i18n.t('Failed to load chat preview'));
|
||||||
|
selectedChat = null;
|
||||||
|
messages = null;
|
||||||
|
history = null;
|
||||||
|
selectedModels = [''];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const searchHandler = async () => {
|
const searchHandler = async () => {
|
||||||
if (searchDebounceTimeout) {
|
if (searchDebounceTimeout) {
|
||||||
clearTimeout(searchDebounceTimeout);
|
clearTimeout(searchDebounceTimeout);
|
||||||
|
|
@ -76,12 +131,69 @@
|
||||||
searchHandler();
|
searchHandler();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onKeyDown = (e) => {
|
||||||
|
if (e.code === 'Escape') {
|
||||||
|
show = false;
|
||||||
|
onClose();
|
||||||
|
} else if (e.code === 'Enter' && (chatList ?? []).length > 0) {
|
||||||
|
const item = document.querySelector(`[data-arrow-selected="true"]`);
|
||||||
|
if (item) {
|
||||||
|
item?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
show = false;
|
||||||
|
return;
|
||||||
|
} else if (e.code === 'ArrowDown') {
|
||||||
|
const searchInput = document.getElementById('search-input');
|
||||||
|
|
||||||
|
if (searchInput) {
|
||||||
|
// check if focused on the search input
|
||||||
|
if (document.activeElement === searchInput) {
|
||||||
|
searchInput.blur();
|
||||||
|
selectedIdx = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedIdx = Math.min(selectedIdx + 1, (chatList ?? []).length - 1);
|
||||||
|
} else if (e.code === 'ArrowUp') {
|
||||||
|
if (selectedIdx === 0) {
|
||||||
|
const searchInput = document.getElementById('search-input');
|
||||||
|
|
||||||
|
if (searchInput) {
|
||||||
|
// check if focused on the search input
|
||||||
|
if (document.activeElement !== searchInput) {
|
||||||
|
searchInput.focus();
|
||||||
|
selectedIdx = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedIdx = Math.max(selectedIdx - 1, 0);
|
||||||
|
} else {
|
||||||
|
selectedIdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = document.querySelector(`[data-arrow-selected="true"]`);
|
||||||
|
item?.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'instant' });
|
||||||
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
document.addEventListener('keydown', onKeyDown);
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (searchDebounceTimeout) {
|
||||||
|
clearTimeout(searchDebounceTimeout);
|
||||||
|
}
|
||||||
|
document.removeEventListener('keydown', onKeyDown);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal size="md" bind:show>
|
<Modal size="xl" bind:show>
|
||||||
<div class="py-2.5 dark:text-gray-300 text-gray-700">
|
<div class="py-2.5 dark:text-gray-300 text-gray-700">
|
||||||
<div class="px-3.5 pb-1.5">
|
<div class="px-3.5 pb-1.5">
|
||||||
<SearchInput
|
<SearchInput
|
||||||
|
|
@ -116,7 +228,10 @@
|
||||||
|
|
||||||
<!-- <hr class="border-gray-100 dark:border-gray-850 my-1" /> -->
|
<!-- <hr class="border-gray-100 dark:border-gray-850 my-1" /> -->
|
||||||
|
|
||||||
<div class="flex flex-col overflow-y-auto h-80 scrollbar-hidden px-3 pb-1">
|
<div class="flex px-3 pb-1">
|
||||||
|
<div
|
||||||
|
class="flex flex-col overflow-y-auto h-96 md:h-[40rem] max-h-full scrollbar-hidden w-full flex-1"
|
||||||
|
>
|
||||||
{#if chatList}
|
{#if chatList}
|
||||||
{#if chatList.length === 0}
|
{#if chatList.length === 0}
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 text-center px-5">
|
<div class="text-xs text-gray-500 dark:text-gray-400 text-center px-5">
|
||||||
|
|
@ -201,5 +316,33 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
id="chat-preview"
|
||||||
|
class="hidden md:flex md:flex-1 w-full overflow-y-auto h-96 md:h-[40rem] scrollbar-hidden"
|
||||||
|
>
|
||||||
|
{#if messages === null}
|
||||||
|
<div
|
||||||
|
class="w-full h-full flex justify-center items-center text-gray-500 dark:text-gray-400 text-sm"
|
||||||
|
>
|
||||||
|
{$i18n.t('Select a conversation to preview')}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="w-full h-full flex flex-col">
|
||||||
|
<Messages
|
||||||
|
className="h-full flex pt-4 pb-8 w-full"
|
||||||
|
user={$user}
|
||||||
|
readOnly={true}
|
||||||
|
{selectedModels}
|
||||||
|
bind:history
|
||||||
|
bind:messages
|
||||||
|
autoScroll={true}
|
||||||
|
sendPrompt={() => {}}
|
||||||
|
continueResponse={() => {}}
|
||||||
|
regenerateResponse={() => {}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
id="search-input"
|
||||||
class="w-full rounded-r-xl py-1.5 pl-2.5 text-sm bg-transparent dark:text-gray-300 outline-hidden"
|
class="w-full rounded-r-xl py-1.5 pl-2.5 text-sm bg-transparent dark:text-gray-300 outline-hidden"
|
||||||
placeholder={placeholder ? placeholder : $i18n.t('Search')}
|
placeholder={placeholder ? placeholder : $i18n.t('Search')}
|
||||||
bind:value
|
bind:value
|
||||||
|
|
@ -106,6 +107,9 @@
|
||||||
focused = true;
|
focused = true;
|
||||||
initTags();
|
initTags();
|
||||||
}}
|
}}
|
||||||
|
on:blur={() => {
|
||||||
|
focused = false;
|
||||||
|
}}
|
||||||
on:keydown={(e) => {
|
on:keydown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
if (filteredTags.length > 0) {
|
if (filteredTags.length > 0) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue