178 lines · 5.6 KB · TypeScript
RawDownload
import { startTransition, type ReactNode } from "react";import type {  Source,} from "@executor/react";import {  useSource,  useSources,} from "@executor/react";import {  Alert,  Button,  SourceToolExplorer,  parseSourceToolExplorerSearch,  useSourcePluginNavigation,  useSourcePluginRouteParams,  useSourcePluginSearch,  type SourceToolExplorerSearch,} from "@executor/react/plugins"; const LOCAL_TOOLS_SOURCE_KIND = "local-tools"; const localToolsSummary = (  <div className="text-xs text-muted-foreground">    File-backed tools loaded from <code className="rounded bg-muted px-1 py-0.5 font-mono">.executor/tools</code>.    Restart the executor session after adding the first local tool so the source is provisioned.  </div>); function LocalToolsSourceRoute(props: {  children: (source: Source) => ReactNode;}) {  const params = useSourcePluginRouteParams<{ sourceId?: string }>();  const sourceId = typeof params.sourceId === "string" ? params.sourceId : null;  const source = useSource(sourceId ?? "");   if (sourceId === null || source.status === "error") {    return (      <div className="px-6 py-8 text-sm text-destructive">        This local tools source is unavailable.      </div>    );  }   if (source.status === "loading") {    return (      <div className="px-6 py-8 text-sm text-muted-foreground">        Loading source...      </div>    );  }   if (source.data.kind !== LOCAL_TOOLS_SOURCE_KIND) {    return (      <div className="px-6 py-8 text-sm text-destructive">        Expected a <code className="rounded bg-muted px-1 py-0.5 font-mono">local-tools</code> source,        but received <code className="rounded bg-muted px-1 py-0.5 font-mono">{source.data.kind}</code>.      </div>    );  }   return props.children(source.data);} function LocalToolsSourceDetailPage(props: {  source: Source;}) {  const navigation = useSourcePluginNavigation();  const search = parseSourceToolExplorerSearch(    useSourcePluginSearch(),  ) satisfies SourceToolExplorerSearch;  const tab = search.tab === "discover" ? "discover" : "model";  const query = search.query ?? "";   return (    <SourceToolExplorer      sourceId={props.source.id}      title={props.source.name}      kind={props.source.kind}      search={search}      navigate={(next) =>        navigation.updateSearch({          tab: next.tab ?? tab,          ...(next.tool !== undefined            ? { tool: next.tool || undefined }            : { tool: search.tool }),          ...(next.query !== undefined            ? { query: next.query || undefined }            : { query }),        })}      summary={localToolsSummary}    />  );} export function LocalToolsAddPage() {  const sources = useSources();  const navigation = useSourcePluginNavigation();   if (sources.status === "loading") {    return (      <div className="px-6 py-8 text-sm text-muted-foreground">        Loading sources...      </div>    );  }   if (sources.status === "error") {    return (      <div className="px-6 py-8 text-sm text-destructive">        Failed loading sources.      </div>    );  }   const localToolsSource = sources.data.find(    (source) => source.kind === LOCAL_TOOLS_SOURCE_KIND,  ) ?? null;   return (    <div className="min-h-0 flex-1 overflow-y-auto">      <div className="mx-auto w-full max-w-3xl px-6 py-10">        <div className="mb-8">          <h1 className="font-display text-2xl tracking-tight text-foreground">            Local Tools          </h1>          <p className="mt-1.5 text-sm leading-6 text-muted-foreground">            File-backed tools from <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">.executor/tools</code> promoted into a first-class source.          </p>        </div>         <div className="space-y-6 rounded-lg border border-border bg-card p-6 text-sm ring-1 ring-foreground/[0.04]">          <div className="rounded-lg border border-border bg-muted/20 p-4 text-sm leading-6 text-muted-foreground">            <p>              The source is provisioned automatically when the executor session starts and finds              local tool files in <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">.executor/tools</code>.            </p>            <p className="mt-3">              Existing tool path behavior is preserved, so a file like              {" "}<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">.executor/tools/demo.ts</code>              {" "}still appears as <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">tools.demo(...)</code>.            </p>          </div>           <div className="flex flex-wrap items-center gap-3">            {localToolsSource ? (              <Button                type="button"                onClick={() => {                  startTransition(() => {                    void navigation.detail(localToolsSource.id, {                      tab: "model",                    });                  });                }}              >                Open Local Tools Source              </Button>            ) : (              <Alert variant="warning">                No local tools source is active yet. Create a tool in                {" "}<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">.executor/tools</code>                {" "}and restart the executor session.              </Alert>            )}          </div>        </div>      </div>    </div>  );} export function LocalToolsDetailRoute() {  return (    <LocalToolsSourceRoute>      {(source) => <LocalToolsSourceDetailPage source={source} />}    </LocalToolsSourceRoute>  );}