Files
open-webui/src/lib/stores/chatList.ts
T
Timothy Jaeryang Baek 20647bd2d5 chore: format
2026-07-27 00:12:47 -04:00

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([]);
};