Files
Jyotisha/frontend/src/components/sidebar-session-row.tsx
T
Jesse_ChenandCursor 59559d4b24 fix(web): persist thinking, title sessions distinctly, and send follow-ups from the answer
Thinking disappeared on failure and never reached session storage. Keep the
sanitized chain on disk and on errors, and regroup the sidebar around reports,
charts, favorites, and dated history titles.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-22 09:10:47 +08:00

124 lines
4.2 KiB
TypeScript

"use client";
import { Menu } from "@base-ui/react/menu";
import {
Archive,
ArchiveRestore,
MoreHorizontal,
Pencil,
Share2,
Star,
StarOff,
Trash2,
} from "lucide-react";
import { forwardRef } from "react";
import { SidebarMenuButton } from "@/components/ui/sidebar";
import { sessionMutationMenuVisible } from "@/lib/chat-session-persistence";
export type SidebarSession = {
readonly id: string;
readonly title: string;
readonly pinned: boolean;
readonly archived: boolean;
};
export type SidebarSessionControls = {
readonly archivedCount: number;
readonly showingArchived: boolean;
readonly menuSessionId: string | null;
readonly disabled: boolean;
readonly onToggleArchivedView: () => void;
readonly onMenuSessionChange: (sessionId: string | null) => void;
readonly onTogglePinned: (sessionId: string) => void;
readonly onRename: (sessionId: string) => void;
readonly onShare: (sessionId: string) => void;
readonly onToggleArchived: (sessionId: string) => void;
readonly onDelete: (sessionId: string) => void;
};
type SidebarSessionRowProps = {
readonly session: SidebarSession;
readonly active: boolean;
readonly disabled: boolean;
readonly menuOpen: boolean;
readonly onMenuOpenChange: (open: boolean) => void;
readonly onSelect: () => void;
readonly onTogglePinned: () => void;
readonly onRename: () => void;
readonly onShare: () => void;
readonly onToggleArchived: () => void;
readonly onDelete: () => void;
};
export const SidebarSessionRow = forwardRef<HTMLButtonElement, SidebarSessionRowProps>(function SidebarSessionRow({
session,
active,
disabled,
menuOpen,
onMenuOpenChange,
onSelect,
onTogglePinned,
onRename,
onShare,
onToggleArchived,
onDelete,
}, ref) {
return (
<Menu.Root
open={sessionMutationMenuVisible(menuOpen, disabled)}
onOpenChange={onMenuOpenChange}
modal={false}
disabled={disabled}
>
<div
className="session-row"
onContextMenu={(event) => {
event.preventDefault();
if (!disabled) onMenuOpenChange(true);
}}
>
<SidebarMenuButton
ref={ref}
className="session-main"
type="button"
isActive={active}
aria-current={active ? "page" : undefined}
disabled={disabled}
onClick={onSelect}
>
<span className="session-title">
{session.pinned ? <Star aria-label="已收藏" /> : null}
<span className="truncate">{session.title}</span>
</span>
</SidebarMenuButton>
<Menu.Trigger
className="session-menu-trigger"
type="button"
aria-label={`${session.title} 更多操作`}
disabled={disabled}
>
<MoreHorizontal aria-hidden="true" />
</Menu.Trigger>
</div>
<Menu.Portal>
<Menu.Positioner className="session-actions-positioner" side="bottom" align="end" sideOffset={4} collisionPadding={12}>
<Menu.Popup className="session-actions" aria-label={`${session.title} 操作`}>
<Menu.Item className="session-action-item" onClick={onTogglePinned}>
{session.pinned ? <StarOff aria-hidden="true" /> : <Star aria-hidden="true" />}
<span>{session.pinned ? "取消收藏" : "收藏"}</span>
</Menu.Item>
<Menu.Item className="session-action-item" onClick={onRename}><Pencil aria-hidden="true" /><span>重命名</span></Menu.Item>
<Menu.Item className="session-action-item" onClick={onShare}><Share2 aria-hidden="true" /><span>转发</span></Menu.Item>
<Menu.Item className="session-action-item" onClick={onToggleArchived}>
{session.archived ? <ArchiveRestore aria-hidden="true" /> : <Archive aria-hidden="true" />}
<span>{session.archived ? "恢复" : "归档"}</span>
</Menu.Item>
<Menu.Separator className="session-actions-separator" />
<Menu.Item className="session-action-item session-action-danger" onClick={onDelete}><Trash2 aria-hidden="true" /><span>删除</span></Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>
);
});