Files
cobble/src/pages/admin/bots/[bot_id]/(partials)/conversation-header-menu.tsx
T
tben fcad4b49d6 Let a user stop a bot, and say the app is still alpha
The thinking row and the header menu both carry Stop while a bot has work
open for the user anywhere: every pending or processing job of that bot in
their conversations is failed as stopped, its task closed, a Stopped step
recorded and one closing message posted per session, so the thread stops
waiting and the bot goes idle. The handler running the job is aborted from
this process through a registry, so a job stuck waiting on a model stops at
once instead of on its next heartbeat, and a late face change after the
abort is dropped.

The release stage is now said in the app too: Settings > General reports
"Cobble v0.1.5 (alpha)" next to development, and the README and RELEASING.md
say what alpha means for installs.
2026-10-23 10:19:41 +01:00

157 lines
5.7 KiB
TypeScript

import { useContext } from "react";
import Paper from "@moduletrace/twui/elements/Paper";
import Stack from "@moduletrace/twui/layout/Stack";
import Divider from "@moduletrace/twui/layout/Divider";
import { FolderOpen, History, Settings } from "lucide-react";
import type { BUN_SQLITE_COBBLE_WORKSPACES } from "@/db/types/db";
import type { BotWithLastMessage, HarnessOverview } from "@/src/types";
import BotSessionsModal from "@/src/components/general/bot-sessions-modal";
import { AppContext } from "@/src/pages/__root";
import canManageCrew from "@/src/utils/rbac/can-manage-crew";
import isPassThroughUser from "@/src/utils/rbac/is-pass-through-user";
import BotComputersModal from "./bot-computers-modal";
import BotFilesModal from "./bot-files-modal";
import BotTasksModal from "./bot-tasks-modal";
import BotRoutinesModal from "./bot-routines-modal";
import BotModelsModal from "./bot-models-modal";
import HeaderMenuItem from "./header-menu-item";
import NewSessionButton from "./new-session-button";
import StopBotButton from "./stop-bot-button";
type Props = {
bot: BotWithLastMessage;
/**
* The workspace the conversation is in: its name shows under the
* bot's, and the models modal edits its models.
*/
workspace?: BUN_SQLITE_COBBLE_WORKSPACES | null;
/**
* A bot message arrived in another session of this conversation.
*/
has_other_unread?: boolean;
/**
* The computer the bot is on, shown on its row.
*/
computer_name?: string | null;
/**
* The text model the bot runs on, shown on its row.
*/
model_name?: string | null;
/**
* After the models modal saved, so the header shows the new model.
*/
on_model_saved: (harnesses: HarnessOverview[]) => void;
/**
* A row is opening something: the menu closes, leaving only that.
*/
on_open: () => void;
};
/**
* The conversation header's menu: the sessions, a new session, the bot's
* computer, its models, its settings and, while it works, stopping it. A
* row that opens a modal closes the menu first, so the modal never sits
* behind an open menu.
*/
export default function ConversationHeaderMenu({
bot,
workspace,
has_other_unread,
computer_name,
model_name,
on_model_saved,
on_open,
}: Props) {
const { pageProps } = useContext(AppContext);
const can_open_settings =
bot.kind == "coordinator"
? isPassThroughUser({ user_types: pageProps?.user_types })
: canManageCrew({ user_types: pageProps?.user_types });
return (
<Paper
role="menu"
className="min-w-[220px] p-0! py-1 gap-0! items-stretch rounded-default bg-surface dark:bg-surface border-hairline dark:border-hairline shadow-popover! dark:shadow-popover!"
>
<Stack className="w-full gap-0! items-stretch">
<BotSessionsModal
bot={bot}
workspace_id={workspace?.id || null}
has_other_unread={has_other_unread}
target={
<HeaderMenuItem
icon={History}
title="Chat sessions"
hint={has_other_unread ? "New" : undefined}
description={
has_other_unread
? "Sessions: new messages in another session"
: "Sessions"
}
/>
}
target_wrapper_props={{ className: "w-full" }}
on_open={on_open}
/>
<NewSessionButton
menu
bot_id={Number(bot.id)}
workspace_id={workspace?.id || null}
on_open={on_open}
/>
<BotFilesModal
menu
bot={bot}
workspace={workspace}
on_open={on_open}
/>
<BotTasksModal
menu
bot={bot}
workspace={workspace}
on_open={on_open}
/>
<BotRoutinesModal
menu
bot={bot}
workspace={workspace}
on_open={on_open}
/>
<BotComputersModal
menu
bot={bot}
workspace_name={
workspace?.id
? workspace.name || "This workspace"
: null
}
menu_hint={computer_name}
on_open={on_open}
/>
<BotModelsModal
key={bot.id}
menu
bot={bot}
workspace={workspace}
menu_hint={model_name}
on_open={on_open}
onSaved={on_model_saved}
/>
</Stack>
<Divider className="my-1 mx-3 w-auto border-hairline dark:border-hairline" />
<StopBotButton menu bot_name={bot.name} />
{can_open_settings ? (
<>
<Divider className="my-1 mx-3 w-auto border-hairline dark:border-hairline" />
<HeaderMenuItem
icon={Settings}
title="Settings"
description={`${bot.name || "This bot"}'s settings`}
href={`/admin/settings/bots/${bot.id}`}
/>
</>
) : null}
</Paper>
);
}