198 lines · 6.9 KB · TypeScript
RawDownload
import { Badge } from "@executor-js/react/components/badge";import {  CardStack,  CardStackContent,  CardStackEntry,  CardStackEntryActions,  CardStackEntryContent,  CardStackEntryDescription,  CardStackEntryField,  CardStackEntryMedia,  CardStackEntryTitle,} from "@executor-js/react/components/card-stack";import { FieldError } from "@executor-js/react/components/field";import { Input } from "@executor-js/react/components/input";import { Textarea } from "@executor-js/react/components/textarea";import { Skeleton } from "@executor-js/react/components/skeleton";import { IntegrationFavicon } from "@executor-js/react/components/integration-favicon";import { IOSSpinner } from "@executor-js/react/components/spinner";import { Button } from "@executor-js/react/components/button";import {  IntegrationIdentityFieldRows,  type IntegrationIdentity,} from "@executor-js/react/plugins/integration-identity"; export type McpRemoteIntegrationPreview = {  readonly name: string;  readonly serverName: string | null;  readonly connected: boolean;  readonly requiresAuthentication: boolean;  readonly requiresOAuth: boolean;  readonly toolCount: number | null;}; export function McpRemoteIntegrationFields(props: {  readonly url: string;  readonly onUrlChange: (url: string) => void;  readonly identity: IntegrationIdentity;  /** The integration's agent-visible description (prefilled from the server's   *  `instructions` when the probe connected). */  readonly description?: string;  readonly onDescriptionChange?: (value: string) => void;  readonly preview: McpRemoteIntegrationPreview | null;  readonly probing?: boolean;  readonly error?: string | null;  readonly onRetry?: () => void;  readonly namespaceReadOnly?: boolean;  readonly urlDisabled?: boolean;}) {  const previewDescription = props.preview    ? props.preview.connected      ? props.preview.toolCount === null        ? null        : `${props.preview.toolCount} tool${props.preview.toolCount !== 1 ? "s" : ""} available`      : props.preview.requiresOAuth        ? "OAuth required to discover tools"        : props.preview.requiresAuthentication          ? "Authentication required to discover tools"          : "Ready to add"    : null;   if (props.preview) {    return (      <CardStack>        <CardStackContent className="border-t-0">          <CardStackEntry>            <CardStackEntryMedia>              <IntegrationFavicon url={props.url} size={32} />            </CardStackEntryMedia>            <CardStackEntryContent>              <CardStackEntryTitle>                {props.preview.serverName ?? props.preview.name}              </CardStackEntryTitle>              {previewDescription ? (                <CardStackEntryDescription>{previewDescription}</CardStackEntryDescription>              ) : null}            </CardStackEntryContent>            <CardStackEntryActions>              {props.preview.connected ? (                <Badge                  variant="outline"                  className="border-border bg-muted text-[10px] text-foreground"                >                  Connected                </Badge>              ) : props.preview.requiresOAuth ? (                <Badge                  variant="outline"                  className="border-border bg-muted text-[10px] text-muted-foreground"                >                  OAuth required                </Badge>              ) : (                <Badge                  variant="outline"                  className="border-border bg-muted text-[10px] text-muted-foreground"                >                  Auth required                </Badge>              )}            </CardStackEntryActions>          </CardStackEntry>          <IntegrationIdentityFieldRows            identity={props.identity}            namePlaceholder="e.g. Linear"            namespaceReadOnly={props.namespaceReadOnly}          />          {props.onDescriptionChange && (            <CardStackEntryField label="Description">              <Textarea                value={props.description ?? ""}                onChange={(e) =>                  props.onDescriptionChange?.((e.target as HTMLTextAreaElement).value)                }                placeholder="What this server offers and when to reach for it"                rows={2}                maxRows={6}                className="text-sm"              />              <p className="text-[11px] text-muted-foreground">                Agent-visible. Prefilled from the server's instructions when it sends any.              </p>            </CardStackEntryField>          )}          <CardStackEntryField label="Server URL">            <Input              value={props.url}              onChange={(e) => props.onUrlChange((e.target as HTMLInputElement).value)}              placeholder="https://mcp.example.com"              className="w-full font-mono text-sm"              disabled={props.urlDisabled}            />          </CardStackEntryField>        </CardStackContent>      </CardStack>    );  }   if (props.probing) {    return (      <CardStack>        <CardStackContent className="border-t-0">          <CardStackEntry>            <CardStackEntryMedia>              <Skeleton className="size-4 rounded" />            </CardStackEntryMedia>            <CardStackEntryContent>              <Skeleton className="h-4 w-40" />              <Skeleton className="mt-1 h-3 w-32" />            </CardStackEntryContent>            <CardStackEntryActions>              <Skeleton className="h-4 w-20 rounded-full" />            </CardStackEntryActions>          </CardStackEntry>        </CardStackContent>      </CardStack>    );  }   return (    <CardStack>      <CardStackContent className="border-t-0">        <CardStackEntryField label="Server URL">          <div className="relative">            <Input              value={props.url}              onChange={(e) => props.onUrlChange((e.target as HTMLInputElement).value)}              placeholder="https://mcp.example.com"              className="w-full pr-9 font-mono text-sm"              aria-invalid={props.error ? true : undefined}              disabled={props.urlDisabled}            />            {props.probing && (              <div className="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2">                <IOSSpinner className="size-4" />              </div>            )}          </div>          {props.error && (            <div className="mt-2 space-y-2">              <FieldError>{props.error}</FieldError>              {props.onRetry && (                <Button                  type="button"                  variant="outline"                  size="sm"                  onClick={props.onRetry}                  className="h-7 px-2 text-xs"                >                  Try again                </Button>              )}            </div>          )}        </CardStackEntryField>      </CardStackContent>    </CardStack>  );}