mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-13 01:02:25 -06:00
160 lines
4.4 KiB
TypeScript
160 lines
4.4 KiB
TypeScript
import { get, readonly, writable } from 'svelte/store';
|
|
import { getChatList, getPinnedChatList } from '$lib/apis/chats';
|
|
|
|
type ChatListItem = {
|
|
id: string;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
const chatsStore = writable<ChatListItem[] | null>(null);
|
|
const pinnedChatsStore = writable<ChatListItem[]>([]);
|
|
|
|
export const chats = readonly(chatsStore);
|
|
export const pinnedChats = readonly(pinnedChatsStore);
|
|
|
|
let currentPage = 1;
|
|
let paginationReady = false;
|
|
let requestGeneration = 0;
|
|
let allLoaded = false;
|
|
let loadingNextPage = false;
|
|
|
|
type RefreshChatListOptions = {
|
|
refreshPinned?: boolean;
|
|
clearPinned?: boolean;
|
|
};
|
|
|
|
type ChatListResult = {
|
|
accepted: boolean;
|
|
allLoaded: boolean;
|
|
};
|
|
|
|
export const refreshChatList = async (
|
|
token: string = '',
|
|
options: RefreshChatListOptions = {}
|
|
): Promise<ChatListResult> => {
|
|
const generation = ++requestGeneration;
|
|
paginationReady = false;
|
|
loadingNextPage = false;
|
|
|
|
const [nextChats, nextPinnedChats] = await Promise.all([
|
|
getChatList(token, 1) as Promise<ChatListItem[]>,
|
|
options.refreshPinned && !options.clearPinned
|
|
? (getPinnedChatList(token) as Promise<ChatListItem[]>)
|
|
: Promise.resolve(undefined as ChatListItem[] | undefined)
|
|
]);
|
|
|
|
if (generation !== requestGeneration) {
|
|
return { accepted: false, allLoaded };
|
|
}
|
|
|
|
chatsStore.set(nextChats);
|
|
currentPage = 1;
|
|
allLoaded = nextChats.length === 0;
|
|
|
|
if (options.clearPinned) {
|
|
pinnedChatsStore.set([]);
|
|
} else if (options.refreshPinned) {
|
|
pinnedChatsStore.set(nextPinnedChats ?? []);
|
|
}
|
|
|
|
paginationReady = true;
|
|
return { accepted: true, allLoaded };
|
|
};
|
|
|
|
// The sidebar's folders keep their chat lists in local component state, refreshed
|
|
// through a registry of per-folder callbacks that only the sidebar can reach.
|
|
// Handlers registered here let other components (e.g. the open chat's menu)
|
|
// request that refresh without a reference to the sidebar.
|
|
type FolderRefreshHandler = (folderId?: string | null, chat?: ChatListItem | null) => unknown;
|
|
const folderRefreshHandlers = new Set<FolderRefreshHandler>();
|
|
|
|
export const registerFolderRefreshHandler = (handler: FolderRefreshHandler) => {
|
|
folderRefreshHandlers.add(handler);
|
|
return () => {
|
|
folderRefreshHandlers.delete(handler);
|
|
};
|
|
};
|
|
|
|
export const refreshFolderChatLists = async (
|
|
folderId?: string | null,
|
|
chat?: ChatListItem | null
|
|
) => {
|
|
await Promise.all([...folderRefreshHandlers].map((handler) => handler(folderId, chat)));
|
|
};
|
|
|
|
export const loadNextChatListPage = async (token: string = ''): Promise<ChatListResult> => {
|
|
if (!paginationReady || allLoaded || loadingNextPage) {
|
|
return { accepted: false, allLoaded };
|
|
}
|
|
|
|
const generation = requestGeneration;
|
|
const nextPage = currentPage + 1;
|
|
loadingNextPage = true;
|
|
|
|
try {
|
|
const nextChats = (await getChatList(token, nextPage)) as ChatListItem[];
|
|
|
|
if (generation !== requestGeneration) {
|
|
return { accepted: false, allLoaded };
|
|
}
|
|
|
|
allLoaded = nextChats.length === 0;
|
|
currentPage = nextPage;
|
|
|
|
const existingIds = new Set((get(chatsStore) ?? []).map((chat) => chat.id));
|
|
const uniqueChats = nextChats.filter((chat) => !existingIds.has(chat.id));
|
|
chatsStore.set([...(get(chatsStore) ?? []), ...uniqueChats]);
|
|
|
|
return { accepted: true, allLoaded };
|
|
} finally {
|
|
loadingNextPage = false;
|
|
}
|
|
};
|
|
|
|
export const setChatActive = (chatId: string, active: boolean): boolean => {
|
|
let found = false;
|
|
const updateChat = (chat: ChatListItem) => {
|
|
if (chat.id !== chatId) {
|
|
return chat;
|
|
}
|
|
found = true;
|
|
return { ...chat, active };
|
|
};
|
|
|
|
chatsStore.update((items) => (items ? items.map(updateChat) : items));
|
|
pinnedChatsStore.update((items) => items.map(updateChat));
|
|
return found;
|
|
};
|
|
|
|
export const setChatReadAt = (chatId: string, lastReadAt: number): boolean => {
|
|
let found = false;
|
|
const updateChat = (chat: ChatListItem) => {
|
|
if (chat.id !== chatId) {
|
|
return chat;
|
|
}
|
|
found = true;
|
|
return { ...chat, last_read_at: lastReadAt };
|
|
};
|
|
|
|
chatsStore.update((items) => (items ? items.map(updateChat) : items));
|
|
pinnedChatsStore.update((items) => items.map(updateChat));
|
|
return found;
|
|
};
|
|
|
|
export const setAllChatsRead = () => {
|
|
const updateChat = (chat: ChatListItem) => ({ ...chat, last_read_at: chat.updated_at });
|
|
|
|
chatsStore.update((items) => (items ? items.map(updateChat) : items));
|
|
pinnedChatsStore.update((items) => items.map(updateChat));
|
|
};
|
|
|
|
export const resetChatListState = () => {
|
|
requestGeneration += 1;
|
|
currentPage = 1;
|
|
paginationReady = false;
|
|
allLoaded = false;
|
|
loadingNextPage = false;
|
|
chatsStore.set(null);
|
|
pinnedChatsStore.set([]);
|
|
};
|