/** * @file SessionRowMenu — the right-click menu of a session anywhere outside a * workspace's own lists (the Sessions table, the Dashboard's session and agent * cards): open its analysis page, open it in the workspace that owns it * (`sessions.lane_id` — an ended session opens its conversation there, never * a resume), copy its id or folder. Inside a workspace, `lanes/SessionMenu` * is used instead. * @author Nguyễn Ngọc Trí Vĩ */ import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { ContextMenu, MenuItem, MenuSeparator, type MenuPoint } from "./ContextMenu"; import { copyText } from "../lib/clipboard"; export type SessionRowRef = { id: string; lane_id?: number | null; cwd?: string | null }; /** * Where "Open in workspace" goes for a session: its live pane when a dashboard * run holds it (`&run=`), else its workspace's replay (`&session=`); null when * no workspace owns it. */ export function workspaceHref( session: SessionRowRef, runs: ReadonlyArray<{ id: string; laneId: number | null; sessionId: string | null }> = [] ): string | null { const run = runs.find((r) => r.sessionId === session.id && r.laneId != null); if (run) return `/workspace?lane=${run.laneId}&run=${encodeURIComponent(run.id)}`; if (session.lane_id != null) return `/workspace?lane=${session.lane_id}&session=${encodeURIComponent(session.id)}`; return null; } export function SessionRowMenu({ session, x, y, onClose, }: MenuPoint & { session: SessionRowRef; onClose: () => void }) { const { t } = useTranslation("sessions"); const navigate = useNavigate(); const pick = (fn: () => unknown) => () => { onClose(); fn(); }; const copy = (text: string) => pick(() => void copyText(text)); return ( navigate(`/sessions/${session.id}`))}> {t("menu.details")} {session.lane_id != null && ( navigate(workspaceHref(session)!))}> {t("menu.inWorkspace")} )} {t("menu.copyId")} {session.cwd && {t("menu.copyFolder")}} ); }