Open the composer's tools in a modal behind one counting button
The chips under the composer clogged it once a bot had a handful of MCP servers and tools. They are now one button reading "N tools" (with an alert icon when one is off or not working) that opens them in a modal, where any the bot was given can still be removed and more added.
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { useState } from "react";
|
||||
import Modal from "@moduletrace/twui/elements/Modal";
|
||||
import Button from "@moduletrace/twui/layout/Button";
|
||||
import H2 from "@moduletrace/twui/layout/H2";
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import Stack from "@moduletrace/twui/layout/Stack";
|
||||
import { CircleAlert, Wrench } from "lucide-react";
|
||||
import ExtensionAddSelect from "@/src/components/general/extension-add-select";
|
||||
import type { BotExtension, BotExtensionsPayload } from "@/src/types";
|
||||
import ComposerToolRow from "./composer-tool-row";
|
||||
|
||||
type Props = {
|
||||
bot_name: string;
|
||||
attached: BotExtension[];
|
||||
addable: BotExtensionsPayload["addable"];
|
||||
can_manage: boolean;
|
||||
/**
|
||||
* The extension being changed, as `kind:id`.
|
||||
*/
|
||||
busy?: string;
|
||||
onAdd: (params: { kind: "mcp" | "tool"; id: number }) => Promise<boolean>;
|
||||
onRemove: (params: {
|
||||
kind: "mcp" | "tool";
|
||||
id: number;
|
||||
name: string;
|
||||
}) => Promise<boolean>;
|
||||
};
|
||||
|
||||
/**
|
||||
* The composer's tools button: how many MCP servers and tools the bot can
|
||||
* use here, opening them in a modal rather than filling the composer with
|
||||
* chips. Super admins and admins remove any it was given (even one every
|
||||
* bot started with) and add more; a workspace's own stay.
|
||||
*/
|
||||
export default function ComposerToolsModal({
|
||||
bot_name,
|
||||
attached,
|
||||
addable,
|
||||
can_manage,
|
||||
busy,
|
||||
onAdd,
|
||||
onRemove,
|
||||
}: Props) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const count = attached.length;
|
||||
const has_problem = attached.some(
|
||||
(item) => !item.enabled || item.state == "error",
|
||||
);
|
||||
const TriggerIcon = has_problem ? CircleAlert : Wrench;
|
||||
const label = count ? `${count} ${count == 1 ? "tool" : "tools"}` : "Add tools";
|
||||
|
||||
return (
|
||||
<Modal
|
||||
target={
|
||||
<Button
|
||||
title={`The tools ${bot_name} can use here`}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
color="gray"
|
||||
size="small"
|
||||
beforeIcon={<TriggerIcon size={12} aria-hidden />}
|
||||
className="shrink-0 gap-1! px-2! py-0.5! text-[12px] leading-4 text-ink-secondary"
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
}
|
||||
targetWrapperProps={{ className: "shrink-0" }}
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
className="max-w-lg bg-paper p-6"
|
||||
>
|
||||
<Stack className="w-full gap-1! items-stretch">
|
||||
<H2 className="text-lg! leading-6! tracking-normal! font-bold text-ink">
|
||||
{`Tools ${bot_name} can use here`}
|
||||
</H2>
|
||||
<Span className="text-[13px] leading-[18px] text-ink-secondary">
|
||||
{count
|
||||
? "MCP servers and tools it was given, plus this workspace's. Remove one to take it away, or add another below."
|
||||
: "It has none yet. Give it an MCP server or tool below, or make one under Settings."}
|
||||
</Span>
|
||||
</Stack>
|
||||
|
||||
{count ? (
|
||||
<Stack
|
||||
aria-label={`Tools ${bot_name} can use here`}
|
||||
className="w-full mt-4 py-1 gap-0! items-stretch rounded-default border border-solid border-hairline bg-surface"
|
||||
>
|
||||
{attached.map((item) => (
|
||||
<ComposerToolRow
|
||||
key={`${item.kind}:${item.id}`}
|
||||
item={item}
|
||||
busy={busy == `${item.kind}:${item.id}`}
|
||||
onRemove={
|
||||
can_manage && item.source == "bot"
|
||||
? () =>
|
||||
onRemove({
|
||||
kind: item.kind,
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
) : null}
|
||||
|
||||
{can_manage && addable.length ? (
|
||||
<Stack className="w-full mt-4 gap-2! items-stretch">
|
||||
<Span className="text-[13px] leading-[18px] text-ink-secondary">
|
||||
{`Give ${bot_name} another one. It can use it everywhere it works, for everyone it works for.`}
|
||||
</Span>
|
||||
<ExtensionAddSelect
|
||||
id="composer-add-extension"
|
||||
addable={addable}
|
||||
loading={Boolean(busy)}
|
||||
onAdd={onAdd}
|
||||
/>
|
||||
</Stack>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user