138e706dd1
A pane's split right/down menus offered only New terminal. They now list the same AI tools as the tab bar's + menu (newTabTools/newTabBody, shared from ConsoleTabs), started bare in the first free slot on that side of the pane. The tab bar's Tools menu is gone: its readme run hit 409 ERUNLIVE whenever slot 0 was live (always, in the session view) and depended on a personal skill; MCP sync and the dev-sync check served the dormant ship-feature-lane pipeline. The sidebar row menu's readme item goes too. The routes stay, reached by `agenthub lanes mcp sync` and `agenthub lanes sync-base`. Also: the home's Memory header and file tabs no longer shrink (a long file squeezed the tab row and clipped it).
82 lines
2.8 KiB
TypeScript
82 lines
2.8 KiB
TypeScript
/**
|
|
* @file WorkspaceRowMenu — the sidebar workspace row's context menu, opened at
|
|
* the pointer (right-click) or under the row's `⋯` button. Every item is an
|
|
* existing call: Start/Stop (`POST /api/lanes/:id/start` slot 0 / `stop`),
|
|
* Rename (the row's inline field), New workspace here (the Add modal prefilled),
|
|
* Copy path, and Reset/Remove through `DestructiveLaneModal` — Reset only on a
|
|
* managed lane. The server's guards are the real ones; the menu only hides
|
|
* what the lane's kind or tool makes meaningless. The shell (position,
|
|
* Escape / outside-click close) is `ContextMenu`.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { api } from "../../lib/api";
|
|
import type { Lane } from "../../lib/types";
|
|
import { ContextMenu, MenuItem, MenuSeparator } from "../ContextMenu";
|
|
|
|
export type WorkspaceMenuAction = "rename" | "new-here" | "reset" | "remove" | "started";
|
|
|
|
export function WorkspaceRowMenu({
|
|
lane,
|
|
x,
|
|
y,
|
|
onClose,
|
|
onAction,
|
|
}: {
|
|
lane: Lane;
|
|
x: number;
|
|
y: number;
|
|
onClose: () => void;
|
|
/** Items the tree itself carries out (rename field, modals, selection). */
|
|
onAction: (action: WorkspaceMenuAction) => void;
|
|
}) {
|
|
const { t } = useTranslation("lanes");
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const run = async (fn: () => Promise<unknown>, then?: WorkspaceMenuAction) => {
|
|
setError(null);
|
|
try {
|
|
await fn();
|
|
if (then) onAction(then);
|
|
onClose();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : String(err));
|
|
}
|
|
};
|
|
const live = !!lane.run_id || lane.status === "running";
|
|
return (
|
|
<ContextMenu x={x} y={y} onClose={onClose} testId={`workspace-row-menu-${lane.id}`}>
|
|
<MenuItem
|
|
onSelect={() =>
|
|
live
|
|
? void run(() => api.lanes.action(lane.id, "stop"))
|
|
: void run(() => api.lanes.action(lane.id, "start", { slot: 0, prompt: "" }), "started")
|
|
}
|
|
>
|
|
{live ? t("menu.stop") : t("menu.start")}
|
|
</MenuItem>
|
|
<MenuItem onSelect={() => onAction("rename")}>{t("menu.rename")}</MenuItem>
|
|
<MenuItem onSelect={() => onAction("new-here")}>{t("menu.newHere")}</MenuItem>
|
|
<MenuItem onSelect={() => void run(() => navigator.clipboard.writeText(lane.cwd))}>
|
|
{t("menu.copyPath")}
|
|
</MenuItem>
|
|
<MenuSeparator />
|
|
{lane.kind === "managed" && (
|
|
<MenuItem danger onSelect={() => onAction("reset")}>
|
|
{t("menu.reset")}
|
|
</MenuItem>
|
|
)}
|
|
<MenuItem danger onSelect={() => onAction("remove")}>
|
|
{t("menu.remove")}
|
|
</MenuItem>
|
|
{error && (
|
|
<p role="alert" className="px-2 py-1 text-[11px] text-status-danger">
|
|
{error}
|
|
</p>
|
|
)}
|
|
</ContextMenu>
|
|
);
|
|
}
|