Give bots tools and MCP servers per bot and workspace, with status and tests
- 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]>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user