- Bots and workspaces get tools and MCP servers from their settings pages; bots also from chips under the chat composer, removable there - "Every bot" now gives one to every bot now and every new bot; each can still drop it (attach-every-bot-extensions.ts moves old installs over) - Status lines: MCP servers record whether they connect, tools whether their last run worked - Test connection in the MCP form and a Test panel in the tool form check the form as it is, without saving - Hide Google sign-in on the login form Co-Authored-By: Claude Opus 5.5 <[email protected]>
78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
import { useState } from "react";
|
|
import postAdminApi from "@/src/layouts/bots/(functions)/post-admin-api";
|
|
import type { BotExtension, BotExtensionsPayload } from "@/src/types";
|
|
|
|
type Params = {
|
|
/**
|
|
* Whose they are: a bot's (`workspace_id` is where it's seen, so that
|
|
* workspace's show too) or a workspace's.
|
|
*/
|
|
owner: { bot_id: number; workspace_id?: number | null } | { workspace_id: number };
|
|
/**
|
|
* The bot's or workspace's name, for the confirmations.
|
|
*/
|
|
owner_name: string;
|
|
initial?: BotExtensionsPayload | null;
|
|
};
|
|
|
|
type Status = { error?: boolean; success?: boolean; msg?: string };
|
|
|
|
/**
|
|
* A bot's or workspace's MCP servers and tools, with adding and removing
|
|
* them (each asks first). Shared by the bot and workspace settings pages
|
|
* and the chat's composer.
|
|
*/
|
|
export default function useExtensions({ owner, owner_name, initial }: Params) {
|
|
const [data, setData] = useState<BotExtensionsPayload>(
|
|
initial || { attached: [], addable: [], can_manage: false },
|
|
);
|
|
const [busy, setBusy] = useState<string>();
|
|
const [status, setStatus] = useState<Status>();
|
|
const api = "bot_id" in owner ? "bots" : "workspaces";
|
|
|
|
const change = async ({
|
|
action,
|
|
kind,
|
|
id,
|
|
}: {
|
|
action: "add" | "remove";
|
|
kind: BotExtension["kind"];
|
|
id: number;
|
|
}) => {
|
|
setBusy(`${kind}:${id}`);
|
|
setStatus(undefined);
|
|
|
|
const res = await postAdminApi<BotExtensionsPayload>({
|
|
path: `${api}/${action}-extension`,
|
|
body: { ...owner, kind, id },
|
|
});
|
|
|
|
setBusy(undefined);
|
|
|
|
if (!res.success || !res.singleRes) {
|
|
setStatus({ error: true, msg: res.msg || "That didn't work." });
|
|
return false;
|
|
}
|
|
|
|
setData(res.singleRes);
|
|
setStatus({ success: true, msg: res.msg || "Saved." });
|
|
return true;
|
|
};
|
|
|
|
const add = async ({ kind, id }: { kind: BotExtension["kind"]; id: number }) => {
|
|
const item = data.addable.find((entry) => entry.kind == kind && entry.id == id);
|
|
|
|
if (!window.confirm(`Give ${owner_name} ${item?.name || "this"}?`)) return false;
|
|
|
|
return change({ action: "add", kind, id });
|
|
};
|
|
|
|
const remove = async ({ kind, id, name }: { kind: BotExtension["kind"]; id: number; name: string }) => {
|
|
if (!window.confirm(`Remove ${name} from ${owner_name}?`)) return false;
|
|
|
|
return change({ action: "remove", kind, id });
|
|
};
|
|
|
|
return { ...data, busy, status, add, remove };
|
|
}
|