export type ForkGroupingItem = { kind: "chat" | "search"; id: string; parentChatId: string | null; }; type ChatTitleState = { title: string | null; titleGenerationPending: boolean; }; type ForkPresentationItem = ForkGroupingItem & { updatedAt: string; starred: boolean; starredAt: string | null; }; export function groupForkedChatItems(items: T[]): T[][] { const groups = new Map }>(); items.forEach((item, index) => { const groupKey = item.kind === "chat" ? `chat:${item.parentChatId ?? item.id}` : `search:${item.id}`; const group = groups.get(groupKey); if (group) { group.items.push({ item, index }); return; } groups.set(groupKey, { firstIndex: index, items: [{ item, index }] }); }); return [...groups.values()] .sort((a, b) => a.firstIndex - b.firstIndex) .map((group) => group.items .sort((a, b) => { const aIsRoot = a.item.kind === "chat" && a.item.parentChatId === null; const bIsRoot = b.item.kind === "chat" && b.item.parentChatId === null; if (aIsRoot !== bIsRoot) return aIsRoot ? -1 : 1; return a.index - b.index; }) .map(({ item }) => item) ); } export function filterSidebarItemsWithForkGroups(items: T[], matches: (item: T) => boolean): T[] { return groupForkedChatItems(items) .flatMap((group) => { const matchingItems = group.filter(matches); if (!matchingItems.length) return []; const root = group.find((item) => item.kind === "chat" && item.parentChatId === null); if (!root || matchingItems.includes(root)) return group; return group.filter((item) => item === root || matchingItems.includes(item)); }); } export function getForkGroupPresentation(groupItems: T[], allItems: T[]) { const firstItem = groupItems[0]; if (!firstItem) return null; const rootId = firstItem.kind === "chat" ? firstItem.parentChatId ?? firstItem.id : null; const familyItems = rootId ? allItems.filter((item) => item.kind === "chat" && (item.id === rootId || item.parentChatId === rootId)) : groupItems; const updatedAt = familyItems.reduce( (newest, item) => (new Date(item.updatedAt).getTime() > new Date(newest).getTime() ? item.updatedAt : newest), firstItem.updatedAt ); const starOwner = rootId ? familyItems.find((item) => item.kind === "chat" && item.parentChatId === null) : firstItem; return { updatedAt, starred: starOwner?.starred ?? false, starredAt: starOwner?.starredAt ?? null, }; } export function shouldRequestChatTitle(chat: ChatTitleState | null | undefined, isRequestInFlight: boolean) { if (!chat || isRequestInFlight) return false; return chat.titleGenerationPending || !chat.title?.trim(); }