mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-13 01:02:25 -06:00
refac
This commit is contained in:
@@ -57,6 +57,7 @@
|
||||
items={folderOptions.map((folder) => ({ value: folder.id, label: folderName(folder) }))}
|
||||
placeholder={$i18n.t('Choose folder')}
|
||||
{align}
|
||||
{side}
|
||||
triggerClass="relative h-8 max-w-[11rem] flex items-center gap-1.5 px-2.5 py-1.5 bg-transparent rounded-2xl text-xs font-normal text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
|
||||
contentClass="w-72 shadow-lg"
|
||||
maxHeight="18rem"
|
||||
|
||||
@@ -952,6 +952,9 @@
|
||||
if ($chatId && !$temporaryChatEnabled && hasPendingAssistantLeaf()) {
|
||||
await loadChat();
|
||||
}
|
||||
if ($chatId && !$temporaryChatEnabled) {
|
||||
updateLastReadAt($chatId);
|
||||
}
|
||||
}
|
||||
} else if (type === 'chat:completion') {
|
||||
chatCompletionEventHandler(data, message, event.chat_id);
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
import { getContext } from 'svelte';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
const i18n: Writable<any> = getContext('i18n');
|
||||
type ChatListI18n = {
|
||||
t: (key: string, options?: Record<string, unknown>) => string;
|
||||
};
|
||||
|
||||
const i18n: Writable<ChatListI18n> = getContext('i18n');
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
@@ -13,11 +17,24 @@
|
||||
import ChevronRight from '$lib/components/icons/ChevronRight.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import { chatId } from '$lib/stores';
|
||||
import { chatId, socket } from '$lib/stores';
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
export let chats: any[] = [];
|
||||
type ChatListItem = {
|
||||
id: string;
|
||||
title?: string;
|
||||
updated_at?: number | null;
|
||||
created_at?: number | null;
|
||||
last_read_at?: number | null;
|
||||
active?: boolean;
|
||||
time_range?: string;
|
||||
user_id?: string;
|
||||
owner_name?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export let chats: ChatListItem[] = [];
|
||||
|
||||
export let chatListLoading = false;
|
||||
export let showOwnerInfo = false;
|
||||
@@ -26,10 +43,10 @@
|
||||
export let perPage = 10;
|
||||
export let orderBy: 'title' | 'updated_at' = 'updated_at';
|
||||
export let direction: 'asc' | 'desc' = 'desc';
|
||||
export let onPageChange: Function = () => {};
|
||||
export let onSort: Function = () => {};
|
||||
export let onPageChange: (page: number) => void | Promise<void> = () => {};
|
||||
export let onSort: (key: 'title' | 'updated_at') => void | Promise<void> = () => {};
|
||||
|
||||
let chatList: any[] | null = null;
|
||||
let chatList: ChatListItem[] | null = null;
|
||||
let totalPages = 1;
|
||||
let pages: (number | 'ellipsis')[] = [];
|
||||
|
||||
@@ -48,6 +65,22 @@
|
||||
onSort(key);
|
||||
};
|
||||
|
||||
const markChatRead = (chat: ChatListItem, unread: boolean) => {
|
||||
if (!unread) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastReadAt = Date.now() / 1000;
|
||||
chatList = (chatList ?? []).map((item) =>
|
||||
item.id === chat.id ? { ...item, last_read_at: lastReadAt } : item
|
||||
);
|
||||
|
||||
$socket?.emit('events:chat', {
|
||||
chat_id: chat.id,
|
||||
data: { type: 'last_read_at' }
|
||||
});
|
||||
};
|
||||
|
||||
const buildPages = (currentPage: number, pageCount: number): (number | 'ellipsis')[] => {
|
||||
if (pageCount <= 7) {
|
||||
return Array.from({ length: pageCount }, (_, i) => i + 1);
|
||||
@@ -177,6 +210,7 @@
|
||||
class=" w-full flex justify-between items-center rounded-lg text-sm py-2 px-3 hover:bg-gray-50 dark:hover:bg-gray-850"
|
||||
draggable="false"
|
||||
href={`/c/${chat.id}`}
|
||||
on:click={() => markChatRead(chat, unread)}
|
||||
>
|
||||
<div class="flex min-w-0 items-center w-full sm:basis-3/5">
|
||||
{#if chat.active}
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from 'svelte';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const i18n: Writable<any> = getContext('i18n');
|
||||
|
||||
import { user } from '$lib/stores';
|
||||
|
||||
import { fade } from 'svelte/transition';
|
||||
import { socket, user } from '$lib/stores';
|
||||
|
||||
import ChatList from './ChatList.svelte';
|
||||
import FolderKnowledge from './FolderKnowledge.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import { getSharedFolderChats } from '$lib/apis/folders';
|
||||
|
||||
export let folder: any = null;
|
||||
type FolderPlaceholderFolder = {
|
||||
id?: string;
|
||||
shared?: boolean;
|
||||
user_id?: string;
|
||||
access_grants?: unknown[];
|
||||
};
|
||||
|
||||
type FolderChat = {
|
||||
id: string;
|
||||
active?: boolean;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export let folder: FolderPlaceholderFolder | null = null;
|
||||
|
||||
let selectedTab = 'chats';
|
||||
|
||||
@@ -24,8 +32,9 @@
|
||||
let direction: 'asc' | 'desc' = 'desc';
|
||||
let currentFolderId: string | null = null;
|
||||
|
||||
let chats: any[] | null = null;
|
||||
let chats: FolderChat[] | null = null;
|
||||
let chatListLoading = false;
|
||||
let refreshQueued = false;
|
||||
|
||||
$: showOwnerInfo = Boolean(
|
||||
folder?.shared ||
|
||||
@@ -53,9 +62,44 @@
|
||||
setChatList();
|
||||
};
|
||||
|
||||
const setChatList = async () => {
|
||||
const updateChatActive = (chatId: string, active: boolean) => {
|
||||
if (!chats) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let found = false;
|
||||
chats = chats.map((chat) => {
|
||||
if (chat.id !== chatId) {
|
||||
return chat;
|
||||
}
|
||||
found = true;
|
||||
return { ...chat, active };
|
||||
});
|
||||
return found;
|
||||
};
|
||||
|
||||
const refreshChatListSoon = (resetPage = false) => {
|
||||
if (refreshQueued) {
|
||||
if (resetPage) {
|
||||
page = 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (resetPage) {
|
||||
page = 1;
|
||||
}
|
||||
refreshQueued = true;
|
||||
queueMicrotask(async () => {
|
||||
refreshQueued = false;
|
||||
await setChatList();
|
||||
});
|
||||
};
|
||||
|
||||
const setChatList = async (clear = false) => {
|
||||
const folderId = folder?.id;
|
||||
chats = null;
|
||||
if (clear) {
|
||||
chats = null;
|
||||
}
|
||||
|
||||
if (folderId) {
|
||||
// Always use the shared folder endpoint so owners also see
|
||||
@@ -69,7 +113,6 @@
|
||||
console.error(error);
|
||||
return null;
|
||||
});
|
||||
chatListLoading = false;
|
||||
|
||||
if (res && res.chats) {
|
||||
chats = res.chats;
|
||||
@@ -78,16 +121,43 @@
|
||||
chats = [];
|
||||
totalChats = 0;
|
||||
}
|
||||
chatListLoading = false;
|
||||
} else {
|
||||
chats = [];
|
||||
totalChats = 0;
|
||||
chatListLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const chatEventHandler = (event: {
|
||||
chat_id?: string;
|
||||
data?: { type?: string; data?: { active?: boolean } };
|
||||
}) => {
|
||||
if (event.data?.type === 'chat:active' && event.chat_id) {
|
||||
const active = event.data.data?.active ?? false;
|
||||
if (!updateChatActive(event.chat_id, active) && active) {
|
||||
refreshChatListSoon(true);
|
||||
}
|
||||
} else if (event.data?.type === 'chat:list') {
|
||||
refreshChatListSoon(true);
|
||||
}
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
const socketInstance = $socket;
|
||||
socketInstance?.on('events', chatEventHandler);
|
||||
socketInstance?.on('connect', refreshChatListSoon);
|
||||
|
||||
return () => {
|
||||
socketInstance?.off('events', chatEventHandler);
|
||||
socketInstance?.off('connect', refreshChatListSoon);
|
||||
};
|
||||
});
|
||||
|
||||
$: if (folder?.id && folder.id !== currentFolderId) {
|
||||
currentFolderId = folder.id;
|
||||
page = 1;
|
||||
setChatList();
|
||||
setChatList(true);
|
||||
}
|
||||
|
||||
$: if (!folder?.id && currentFolderId !== null) {
|
||||
|
||||
Reference in New Issue
Block a user