Files
cobble/src/components/general/code-editor.tsx
T
tbenandClaude Opus 5.5 c6ec7d77a2 Add MCP servers and custom tools for bots
Two new settings pages, MCPs and Tools. Super admins define them; super
admins and admins attach them to bots, to workspaces (every bot there),
or to every bot. Bots get them in chat replies and tasks.

- Custom tools: a JSON-schema of arguments plus an HTTP request, a script
  on the bot's computer, or a prompt, templated with {{args.x}} and
  {{env.KEY}}, secrets redacted from results.
- MCP servers: remote (URL and headers) or stdio commands run on the host
  with a setup command and an isolated env, in a per-process pool. Tools
  are listed into a cache and connect on first call.
- Add, edit, access and try open in modals.
- Markdown, JSON and command fields use twui's AceEditor (CodeEditor),
  its scripts loaded on demand.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-19 15:01:39 +01:00

62 lines
1.9 KiB
TypeScript

import { useEffect, useRef } from "react";
import AceEditor from "@moduletrace/twui/editors/AceEditor";
import type AceEditorModes from "@moduletrace/twui/editors/ace-editor-modes";
import Span from "@moduletrace/twui/layout/Span";
import Stack from "@moduletrace/twui/layout/Stack";
import { twMerge } from "tailwind-merge";
import loadAceScripts from "@/src/utils/load-ace-scripts";
type Props = {
title: string;
/**
* The starting text. Changes come back through `onChange`.
*/
content?: string;
mode: (typeof AceEditorModes)[number];
placeholder?: string;
/**
* One line under it, like a field's info.
*/
info?: string;
/**
* Its height, e.g. `h-24`.
*/
className?: string;
onChange: (value: string) => void;
};
/**
* A code field: twui's `AceEditor` with its label, and a line of info
* under it. Loads the editor's scripts when it first shows
* (`loadAceScripts`).
*/
export default function CodeEditor({ title, content, mode, placeholder, info, className, onChange }: Props) {
/**
* The editor keeps the handler it was created with; this keeps it
* calling the latest one.
*/
const change = useRef(onChange);
change.current = onChange;
useEffect(() => {
loadAceScripts().catch(() => {});
}, []);
return (
<Stack className="w-full gap-1.5! items-stretch">
<AceEditor
title={title}
showLabel
content={content}
mode={mode}
placeholder={placeholder}
fontSize="13px"
delay={0}
wrapperProps={{ className: twMerge("h-40", className) }}
onChange={(value) => change.current(value)}
/>
{info ? <Span className="text-[13px] leading-[18px] text-ink-secondary">{info}</Span> : null}
</Stack>
);
}