import { useMemo, useState } from "react";
import { useAtomValue, Result } from "@effect-atom/atom-react";
import { toolSchemaAtom } from "../api/atoms";
import { ScopeId, ToolId } from "@executor/sdk";
import { Button } from "./button";
import { Markdown } from "./markdown";
import { SchemaExplorer } from "./schema-explorer";
import { ExpandableCodeBlock } from "./expandable-code-block";
import { CardStack, CardStackHeader, CardStackContent } from "./card-stack";
import { CopyButton } from "./copy-button";
import { ChevronRight } from "lucide-react";
function EmptySection(props: { title: string; message: string }) {
return (
{props.title}
{props.message}
);
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const friendlyName = (name: string): string => {
const leaf = name.split(".").pop() ?? name;
return leaf
.replace(/([a-z])([A-Z])/g, "$1 $2")
.replace(/[_.-]/g, " ")
.replace(/\b\w/g, (c) => c.toUpperCase());
};
const breadcrumbParts = (name: string): string[] =>
name.split(".").map((p) =>
p
.replace(/([a-z])([A-Z])/g, "$1 $2")
.replace(/[_-]/g, " ")
.replace(/\b\w/g, (c) => c.toUpperCase()),
);
// ---------------------------------------------------------------------------
// ToolDetail
// ---------------------------------------------------------------------------
export function ToolDetail(props: {
toolId: string;
toolName: string;
toolDescription?: string;
scopeId: ScopeId;
}) {
const toolContract = useAtomValue(toolSchemaAtom(props.scopeId, props.toolId as ToolId));
const [tab, setTab] = useState<"schema" | "typescript">("schema");
const data = useMemo(() => {
if (!Result.isSuccess(toolContract)) return null;
const v = toolContract.value;
const definitions = Object.entries(v.typeScriptDefinitions ?? {}).map(([name, body]) => ({
name,
code: body,
}));
return {
inputSchema: v.inputSchema,
outputSchema: v.outputSchema,
inputTypeScript: v.inputTypeScript ? `type Input = ${v.inputTypeScript}` : null,
outputTypeScript: v.outputTypeScript ? `type Output = ${v.outputTypeScript}` : null,
definitions,
};
}, [toolContract]);
const crumbs = breadcrumbParts(props.toolName);
const displayName = friendlyName(props.toolName);
return (
{/* Header + tabs */}
{crumbs.length > 1 && (
{crumbs.slice(0, -1).map((part, i) => (
{i > 0 && }
{part}
))}
)}
{displayName}
{props.toolDescription && (
{props.toolDescription}
)}
{/* Tabs */}
{/* Content */}
{Result.match(toolContract, {
onInitial: () =>
Loading…
,
onFailure: () =>
Something went wrong
,
onSuccess: () =>
tab === "schema" ? (
{data?.inputSchema ? (
) : (
)}
{data?.outputSchema ? (
) : (
)}
) : (
{data?.inputTypeScript ? (
Input
) : (
)}
{data?.outputTypeScript ? (
Output
) : (
)}
),
})}
);
}
// ---------------------------------------------------------------------------
// Empty state
// ---------------------------------------------------------------------------
export function ToolDetailEmpty(props: { hasTools: boolean }) {
return (
{props.hasTools ? "Select a tool" : "No tools available"}
{props.hasTools && (
Choose from the list to see what it does.
)}
);
}