This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
export type ChatSummary = {
|
||||
id: string;
|
||||
title: string | null;
|
||||
parentChatId: string | null;
|
||||
titleGenerationPending: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
starred: boolean;
|
||||
@@ -58,6 +60,8 @@ export type ToolCallEvent = {
|
||||
export type ChatDetail = {
|
||||
id: string;
|
||||
title: string | null;
|
||||
parentChatId: string | null;
|
||||
titleGenerationPending: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
starred: boolean;
|
||||
@@ -291,6 +295,14 @@ export async function getChat(chatId: string) {
|
||||
return data.chat;
|
||||
}
|
||||
|
||||
export async function forkChat(chatId: string, messageId?: string) {
|
||||
const data = await api<{ chat: ChatSummary }>(`/v1/chats/${chatId}/fork`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(messageId ? { messageId } : {}),
|
||||
});
|
||||
return data.chat;
|
||||
}
|
||||
|
||||
export async function updateChatTitle(chatId: string, title: string) {
|
||||
const data = await api<{ chat: ChatSummary }>(`/v1/chats/${chatId}`, {
|
||||
method: "PATCH",
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
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<T extends ForkGroupingItem>(items: T[]): T[][] {
|
||||
const groups = new Map<string, { firstIndex: number; items: Array<{ item: T; index: number }> }>();
|
||||
|
||||
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<T extends ForkGroupingItem>(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<T extends ForkPresentationItem>(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();
|
||||
}
|
||||
Reference in New Issue
Block a user