156 lines
4.4 KiB
TypeScript
156 lines
4.4 KiB
TypeScript
import {
|
|
SidebarContent,
|
|
SidebarGroup,
|
|
SidebarGroupContent,
|
|
SidebarGroupLabel,
|
|
SidebarHeader,
|
|
SidebarMenu,
|
|
SidebarMenuAction,
|
|
SidebarMenuButton,
|
|
SidebarMenuItem,
|
|
} from "@/components/ui/sidebar";
|
|
import {
|
|
ColumnInsertIcon,
|
|
Delete02Icon,
|
|
PencilEdit02Icon,
|
|
} from "@hugeicons/core-free-icons";
|
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
import { db, useLiveQuery } from "./db";
|
|
import type { ChatView, ThreadRecord } from "./types";
|
|
|
|
interface SidebarItem {
|
|
type: "single" | "compare";
|
|
id: string;
|
|
title: string;
|
|
createdAt: number;
|
|
}
|
|
|
|
function groupThreads(threads: ThreadRecord[]): SidebarItem[] {
|
|
const items: SidebarItem[] = [];
|
|
const seenPairs = new Set<string>();
|
|
|
|
for (const t of threads) {
|
|
if (t.archived) {
|
|
continue;
|
|
}
|
|
if (t.pairId) {
|
|
if (seenPairs.has(t.pairId)) {
|
|
continue;
|
|
}
|
|
seenPairs.add(t.pairId);
|
|
items.push({
|
|
type: "compare",
|
|
id: t.pairId,
|
|
title: t.title,
|
|
createdAt: t.createdAt,
|
|
});
|
|
} else if (t.modelType === "base") {
|
|
items.push({
|
|
type: "single",
|
|
id: t.id,
|
|
title: t.title,
|
|
createdAt: t.createdAt,
|
|
});
|
|
}
|
|
}
|
|
|
|
return items.sort((a, b) => b.createdAt - a.createdAt);
|
|
}
|
|
|
|
export function ThreadSidebar({
|
|
view,
|
|
onSelect,
|
|
onNewThread,
|
|
onNewCompare,
|
|
}: {
|
|
view: ChatView;
|
|
onSelect: (view: ChatView) => void;
|
|
onNewThread: () => void;
|
|
onNewCompare: () => void;
|
|
}) {
|
|
const allThreads = useLiveQuery(
|
|
() => db.threads.orderBy("createdAt").reverse().toArray(),
|
|
[],
|
|
);
|
|
const items = groupThreads(allThreads ?? []);
|
|
const activeId = view.mode === "single" ? view.threadId : view.pairId;
|
|
|
|
function viewForItem(item: SidebarItem): ChatView {
|
|
return item.type === "single"
|
|
? { mode: "single", threadId: item.id }
|
|
: { mode: "compare", pairId: item.id };
|
|
}
|
|
|
|
async function handleDelete(item: SidebarItem) {
|
|
if (item.type === "single") {
|
|
await db.messages.where("threadId").equals(item.id).delete();
|
|
await db.threads.delete(item.id);
|
|
} else {
|
|
const paired = await db.threads.where("pairId").equals(item.id).toArray();
|
|
for (const t of paired) {
|
|
await db.messages.where("threadId").equals(t.id).delete();
|
|
await db.threads.delete(t.id);
|
|
}
|
|
}
|
|
if (activeId === item.id) {
|
|
onSelect({ mode: "single" });
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<SidebarHeader className="px-4 py-3">
|
|
<span className="text-base font-semibold tracking-tight">Playground</span>
|
|
</SidebarHeader>
|
|
<SidebarContent>
|
|
<SidebarGroup className="px-4 pt-1">
|
|
<SidebarGroupContent>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton onClick={onNewThread}>
|
|
<HugeiconsIcon icon={PencilEdit02Icon} />
|
|
<span>New Chat</span>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton onClick={onNewCompare}>
|
|
<HugeiconsIcon icon={ColumnInsertIcon} />
|
|
<span>Compare</span>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarGroupContent>
|
|
</SidebarGroup>
|
|
<SidebarGroup className="flex-1 px-4">
|
|
<SidebarGroupLabel className="text-xs font-medium text-muted-foreground/80">Your Chats</SidebarGroupLabel>
|
|
<SidebarGroupContent>
|
|
<SidebarMenu>
|
|
{items.map((item) => (
|
|
<SidebarMenuItem key={item.id}>
|
|
<SidebarMenuButton
|
|
isActive={activeId === item.id}
|
|
onClick={() => onSelect(viewForItem(item))}
|
|
>
|
|
<span>{item.title}</span>
|
|
</SidebarMenuButton>
|
|
<SidebarMenuAction
|
|
showOnHover={true}
|
|
onClick={() => handleDelete(item)}
|
|
title="Delete"
|
|
>
|
|
<HugeiconsIcon icon={Delete02Icon} />
|
|
</SidebarMenuAction>
|
|
</SidebarMenuItem>
|
|
))}
|
|
</SidebarMenu>
|
|
{items.length === 0 && (
|
|
<p className="px-2 py-6 text-center text-xs text-muted-foreground">
|
|
No threads yet
|
|
</p>
|
|
)}
|
|
</SidebarGroupContent>
|
|
</SidebarGroup>
|
|
</SidebarContent>
|
|
</>
|
|
);
|
|
}
|