/** * @file SessionMenu — a session row's right-click menu (sidebar, the workspace * home's Recents, the archived fold), and a bare live tab's (`session` null — * the sidebar's **New session** row). Every item is something the row already * does: open it the way its click does (a live pane attaches, an ended session * opens its conversation — never a resume), open a live one in a split, * rename / pin / unpin / archive / unarchive when the list offers them, copy its id, open * its analysis page, and end its live tab as the tab's buttons do (`endRuns`, * after the same confirm for an AI tab, which may be mid-turn): **Sleep** stops * it and keeps the session listed to resume later, **Close tab** stops it and * archives the session. * @author Nguyễn Ngọc Trí Vĩ */ import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import type { RunHandle } from "../../lib/api"; import type { Session } from "../../lib/types"; import { ContextMenu, MenuItem, MenuSeparator, type MenuPoint } from "../ContextMenu"; import { copyText } from "../../lib/clipboard"; import { canSleep, endRuns } from "../../lib/stopSession"; export function SessionMenu({ laneId, session, run, x, y, onClose, onRename, onArchive, onUnarchive, onPin, }: MenuPoint & { laneId: number; session: Session | null; /** The live pane holding it, if any. */ run: RunHandle | null; onClose: () => void; onRename?: () => void; onArchive?: () => void; onUnarchive?: () => void; /** Pin or unpin (by `session.pinned_at`), where the list offers it. */ onPin?: () => void; }) { const { t } = useTranslation("lanes"); const navigate = useNavigate(); const runUrl = run && `/workspace?lane=${laneId}&run=${encodeURIComponent(run.id)}`; const pick = (fn: () => unknown) => () => { onClose(); fn(); }; // Stop archives the session; Sleep keeps it listed to resume later // (`endRuns` asks first for an AI pane, which may be mid-turn). const endTab = (sleep: boolean) => { if (!run) return; onClose(); void endRuns([run], { sleep, name: session?.name || t("tree.newSession"), t }); }; return ( navigate( runUrl ?? `/workspace?lane=${laneId}&session=${encodeURIComponent(session?.id ?? "")}` ) )} > {t("sessionMenu.open")} {runUrl && ( navigate(runUrl, { state: { split: true } }))}> {t("sessionMenu.openSplit")} )} {onRename && {t("menu.rename")}} {onPin && session && ( {t(session.pinned_at ? "sessionMenu.unpin" : "sessionMenu.pin")} )} {session && ( <> navigate(`/sessions/${session.id}`))}> {t("sessionMenu.details")} void copyText(session.id))}> {t("sessionMenu.copyId")} )} {(run || onArchive || onUnarchive) && } {onUnarchive && {t("tree.unarchive")}} {onArchive && {t("sessionMenu.archive")}} {canSleep(run) && ( endTab(true)}> {t("tabs.sleep")} )} {run && ( endTab(false)}> {t("tabs.close")} )} ); }