import { Plural, Trans, useLingui } from "@lingui/react/macro"; import type { CapabilityInstall, Connection, ConnectionCatalogItem, IntegrationCatalogResult, IntegrationCatalogSurface, } from "@rakazo/contracts"; import { abortableDelay, buildFeaturedConnectorTiles, CONNECTION_CATALOG_PAGE_SIZE, EMPTY_PLUGIN_CATALOG_MESSAGE, filterConnectionCatalogItems, humanizeToolName, } from "@rakazo/core"; import { Button, Card, CardContent, CardHeader, CardTitle, Dialog, DialogClose, DialogContent, DialogHeader, DialogTitle, Input, NativeSelect, NativeSelectOption, } from "@rakazo/ui-web"; import { ChevronDown, ChevronLeft, ChevronUp, X } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; import { IntegrationSetup } from "../components/integrations/IntegrationSetup"; import { optionalCatalogFeedProbe } from "../lib/optional-catalog-feed"; import { rpc } from "../lib/rpc"; type SourceKind = "treg" | "executor" | "mcp" | "api" | "graphql"; type ConnectionTool = { name: string; description: string }; function itemKey(item: Pick) { return `${item.connectorId}:${item.slug}`; } function markConnected( items: ConnectionCatalogItem[], connectorId: string, slug: string, connected: boolean, ) { return items.map((entry) => entry.connectorId === connectorId && entry.slug === slug ? { ...entry, connected } : entry, ); } function activeAccounts( connections: Connection[], item: Pick, ) { return connections.filter( (row) => row.connectorId === item.connectorId && row.provider === item.slug && (row.status === "connected" || row.status === "pending"), ); } function nextAccountLabel(itemName: string, existingCount: number) { return existingCount <= 0 ? itemName : `${itemName} ${existingCount + 1}`; } export function PluginsOverlay({ onClose, onOpenMcp, activeBotId, embedded = false, }: { onClose: () => void; onOpenMcp?: () => void; activeBotId?: string; /** Render panel body only for the shared Settings shell. */ embedded?: boolean; }) { const { t } = useLingui(); const [setupOpen, setSetupOpen] = useState(false); const [query, setQuery] = useState(""); const [visibleCount, setVisibleCount] = useState(CONNECTION_CATALOG_PAGE_SIZE); const [catalog, setCatalog] = useState([]); const [connections, setConnections] = useState([]); const [labelDrafts, setLabelDrafts] = useState>({}); const [sources, setSources] = useState([]); const [sourceKind, setSourceKind] = useState(null); const [sourceName, setSourceName] = useState(""); const [sourceUrl, setSourceUrl] = useState(""); const [credential, setCredential] = useState(""); const [authType, setAuthType] = useState<"none" | "bearer" | "header">("bearer"); const [authName, setAuthName] = useState("x-api-key"); const [pending, setPending] = useState(null); const [catalogError, setCatalogError] = useState(null); const [sourceError, setSourceError] = useState(null); const [sourceHint, setSourceHint] = useState(null); const [catalogFeedEnabled, setCatalogFeedEnabled] = useState(false); const [catalogFeedQuery, setCatalogFeedQuery] = useState(""); const [catalogFeedResults, setCatalogFeedResults] = useState([]); const [catalogFeedError, setCatalogFeedError] = useState(null); const [catalogFeedPending, setCatalogFeedPending] = useState(false); const [catalogFeedSearched, setCatalogFeedSearched] = useState(false); const [loading, setLoading] = useState(true); const [detailKey, setDetailKey] = useState<{ connectorId: string; slug: string } | null>(null); const [tools, setTools] = useState([]); const [toolsLoading, setToolsLoading] = useState(false); const [toolsOpen, setToolsOpen] = useState(true); const [toolsTick, setToolsTick] = useState(0); const connectionAttempt = useRef(null); async function refresh() { const [items, installs, rows, catalogFeed] = await Promise.all([ rpc.connections.catalog({}), rpc.capabilities.list(), rpc.connections.list(), optionalCatalogFeedProbe(rpc.capabilities.catalogSearch({ query: "" })), ]); setCatalog(items); setConnections(rows); setLabelDrafts((current) => { const next: Record = {}; for (const row of rows) { if (row.status === "connected" || row.status === "pending") { next[row.id] = current[row.id] ?? row.displayName; } } return next; }); setSources( installs.filter( (install) => install.kind === "mcp" || install.kind === "api" || install.kind === "graphql", ), ); setCatalogFeedEnabled(catalogFeed.enabled); return items; } useEffect(() => { void refresh() .catch((err: unknown) => setCatalogError(err instanceof Error ? err.message : t`Could not load integrations`), ) .finally(() => setLoading(false)); return () => connectionAttempt.current?.abort(); }, []); const detailItem = useMemo(() => { if (!detailKey) return null; return ( catalog.find( (entry) => entry.connectorId === detailKey.connectorId && entry.slug === detailKey.slug, ) ?? null ); }, [catalog, detailKey]); useEffect(() => { if (!detailKey) { setTools([]); setToolsLoading(false); return; } let cancelled = false; setToolsLoading(true); void rpc.connections .tools({ connectorId: detailKey.connectorId, provider: detailKey.slug }) .then((list) => { if (!cancelled) setTools(list); }) .catch(() => { if (!cancelled) setTools([]); }) .finally(() => { if (!cancelled) setToolsLoading(false); }); return () => { cancelled = true; }; }, [detailKey, toolsTick]); const featuredTiles = useMemo(() => buildFeaturedConnectorTiles(catalog), [catalog]); const showFeatured = !query.trim(); const visible = useMemo(() => filterConnectionCatalogItems(catalog, query), [catalog, query]); const rendered = visible.slice(0, visibleCount); function openDetail(item: ConnectionCatalogItem) { setCatalogError(null); setToolsOpen(true); setDetailKey({ connectorId: item.connectorId, slug: item.slug }); } function closeDetail() { setDetailKey(null); setTools([]); } function itemConnected(item: ConnectionCatalogItem) { return ( item.connected || activeAccounts(connections, item).some((row) => row.status === "connected") ); } async function notifyAppConnected(item: ConnectionCatalogItem) { if (!activeBotId) return; await rpc.onboarding .appConnected({ botId: activeBotId, provider: item.slug, connectorId: item.connectorId }) .catch(() => undefined); } function setItemConnected(item: ConnectionCatalogItem, connected: boolean) { setCatalog((prev) => markConnected(prev, item.connectorId, item.slug, connected)); } async function connect(item: ConnectionCatalogItem) { connectionAttempt.current?.abort(); const controller = new AbortController(); connectionAttempt.current = controller; setCatalogError(null); const key = itemKey(item); setPending(key); try { const existing = activeAccounts(connections, item).filter( (row) => row.status === "connected", ); const started = await rpc.connections.begin({ connectorId: item.connectorId, provider: item.slug, displayName: nextAccountLabel(item.name, existing.length), }); if (started.authorizationUrl) window.open(started.authorizationUrl, "rakazo-plugin-connect", "noopener,noreferrer"); if (item.noAuth && !started.authorizationUrl) { if (controller.signal.aborted) return; setItemConnected(item, true); void notifyAppConnected(item); await refresh().catch(() => undefined); setToolsTick((tick) => tick + 1); return; } for (let i = 0; i < 45; i += 1) { if (controller.signal.aborted) return; const row = await rpc.connections .complete({ connectionId: started.connectionId }) .catch(() => undefined); if (row?.status === "connected") { if (controller.signal.aborted) return; setItemConnected(item, true); void notifyAppConnected(item); await refresh().catch(() => undefined); setToolsTick((tick) => tick + 1); return; } await abortableDelay(2_000, controller.signal); } if (controller.signal.aborted) return; setCatalogError( t`Connection to ${item.name} is still pending. You can close this and check again.`, ); await refresh().catch(() => undefined); } catch (err) { if (controller.signal.aborted) return; setCatalogError(err instanceof Error ? err.message : t`Could not connect`); } finally { if (connectionAttempt.current === controller) { connectionAttempt.current = null; setPending(null); } } } async function revokeAccount(row: Connection, item: ConnectionCatalogItem) { setCatalogError(null); setPending(row.id); try { await rpc.connections.revoke({ connectionId: row.id }); const remaining = activeAccounts(connections, item).filter((entry) => entry.id !== row.id); if (remaining.every((entry) => entry.status !== "connected")) { setItemConnected(item, false); } await refresh().catch(() => undefined); setToolsTick((tick) => tick + 1); } catch (err) { setCatalogError(err instanceof Error ? err.message : t`Could not revoke connection`); } finally { setPending(null); } } async function uninstall(item: ConnectionCatalogItem) { const matches = activeAccounts(connections, item); if (matches.length === 0) { setItemConnected(item, false); closeDetail(); return; } setCatalogError(null); setPending(`uninstall:${itemKey(item)}`); try { for (const row of matches) { await rpc.connections.revoke({ connectionId: row.id }); } setItemConnected(item, false); await refresh().catch(() => undefined); closeDetail(); } catch (err) { setCatalogError(err instanceof Error ? err.message : t`Could not revoke connection`); await refresh().catch(() => undefined); } finally { setPending(null); } } async function renameAccount(row: Connection) { const displayName = (labelDrafts[row.id] ?? row.displayName).trim(); if (!displayName || displayName === row.displayName) return; setPending(`rename:${row.id}`); setCatalogError(null); try { const updated = await rpc.connections.rename({ connectionId: row.id, displayName }); setConnections((current) => current.map((entry) => entry.id === row.id ? { ...entry, displayName: updated.displayName } : entry, ), ); setLabelDrafts((current) => ({ ...current, [row.id]: updated.displayName })); } catch (err) { setCatalogError(err instanceof Error ? err.message : t`Could not rename connection`); } finally { setPending(null); } } function beginSource(kind: SourceKind) { setSourceKind(kind); setSourceError(null); setSourceHint(null); setSourceName(kind === "treg" ? "Treg" : kind === "executor" ? "Executor" : ""); setSourceUrl(kind === "treg" ? "https://treg.to/mcp/" : ""); setCredential(""); setAuthType(kind === "treg" || kind === "executor" ? "bearer" : "none"); setAuthName("x-api-key"); } async function searchCatalogFeed() { setCatalogFeedError(null); setCatalogFeedPending(true); setCatalogFeedSearched(false); setCatalogFeedResults([]); try { const response = await rpc.capabilities.catalogSearch({ query: catalogFeedQuery }); setCatalogFeedResults(response.results); setCatalogFeedSearched(true); } catch (err) { setCatalogFeedError(err instanceof Error ? err.message : t`Could not search catalog`); } finally { setCatalogFeedPending(false); } } function beginCatalogSurface( result: IntegrationCatalogResult, surface: IntegrationCatalogSurface, ) { if (!surface.source || (surface.kind !== "mcp" && surface.kind !== "openapi")) return; setSourceKind(surface.kind === "mcp" ? "mcp" : "api"); setSourceName(result.name); setSourceUrl(surface.source); setCredential(""); setAuthType(surface.auth?.type ?? "none"); setAuthName(surface.auth?.headerName ?? "x-api-key"); setSourceHint(surface.auth?.note ?? null); setSourceError(null); } async function installSource() { if (!sourceKind) return; setSourceError(null); setPending("install-source"); try { const auth = { type: authType, ...(authType === "header" ? { name: authName.trim() } : {}), }; await rpc.capabilities.install({ kind: sourceKind === "treg" || sourceKind === "executor" ? "mcp" : sourceKind, name: sourceName.trim() || (sourceKind === "treg" ? "Treg" : sourceKind === "executor" ? "Executor" : sourceKind === "graphql" ? "GraphQL" : "Custom connector"), source: sourceUrl.trim(), credential: credential.trim() || undefined, config: sourceKind === "treg" ? { preset: "treg", auth: { type: "bearer" } } : sourceKind === "api" ? { openApi: true, auth } : sourceKind === "graphql" ? { auth } : { preset: "custom", auth: sourceKind === "executor" ? { type: "bearer" } : auth, }, }); setCredential(""); setSourceKind(null); await refresh(); } catch (err) { setSourceError(err instanceof Error ? err.message : t`Could not install connector`); } finally { setPending(null); } } async function removeSource(install: CapabilityInstall) { setPending(install.id); setSourceError(null); try { await rpc.capabilities.remove({ id: install.id }); setSources((current) => current.filter((source) => source.id !== install.id)); } catch (err) { setSourceError(err instanceof Error ? err.message : t`Could not remove connector`); } finally { setPending(null); } } function renderCatalogActions(item: ConnectionCatalogItem) { const key = itemKey(item); const connected = itemConnected(item); const connecting = pending === key; if (connected) { return ( ); } return ( ); } function renderCatalogTile( item: ConnectionCatalogItem, label: string, logo?: string | null, opts?: { tileTestId?: boolean }, ) { const connected = itemConnected(item); const tileTestId = opts?.tileTestId !== false && connected; const icon = logo ? ( ) : (
{label[0]}
); const title = (
{label}
); return (
{connected ? ( ) : ( <> {icon} {title} )} {renderCatalogActions(item)}
); } function renderDetail(item: ConnectionCatalogItem) { const accounts = activeAccounts(connections, item); const key = itemKey(item); const connecting = pending === key; const uninstalling = pending === `uninstall:${key}`; const toolCount = tools.length; return (
{item.logo ? ( ) : (
{item.name[0]}
)}
{item.name}
Accounts {accounts.map((row) => (
setLabelDrafts((current) => ({ ...current, [row.id]: event.target.value })) } onBlur={() => void renameAccount(row)} onKeyDown={(event) => { if (event.key === "Enter") { event.currentTarget.blur(); } }} />
))}
{toolsOpen ? ( {toolsLoading ? (

Loading tools…

) : tools.length === 0 ? (

No tools available.

) : (
    {tools.map((tool) => (
  • {humanizeToolName(tool.name)}
  • ))}
)}
) : null}
); } const searchField = !detailItem ? (
{ setQuery(event.target.value); setVisibleCount(CONNECTION_CATALOG_PAGE_SIZE); }} aria-label={t`Search apps`} placeholder={t`Search apps`} className="h-11 rounded-xl px-4 md:text-ui-body" />
) : null; const catalogBody = ( <>
{setupOpen ? (
{ setSetupOpen(false); void refresh().catch((err: unknown) => setCatalogError( err instanceof Error ? err.message : t`Could not load integrations`, ), ); }} />
) : null} {catalogError ?

{catalogError}

: null} {detailItem ? ( renderDetail(detailItem) ) : ( <> {loading ? (

Loading integrations…

) : null} {showFeatured ? (
{!loading && catalog.length === 0 ? (

{EMPTY_PLUGIN_CATALOG_MESSAGE}

) : (
{featuredTiles.map((tile) => { const item = tile.item; const key = item ? itemKey(item) : tile.id; const disabled = tile.missing || !item; if (item && !tile.missing) { // Featured is the stable hit target for connection-tile-* in E2E. return renderCatalogTile(item, tile.label, item.logo, { tileTestId: true, }); } return (
{tile.label[0]}
{tile.label}
{disabled ? (
Not in the plugin catalog
) : null}
); })}
)}
) : null} {!loading && catalog.length === 0 && !showFeatured ? (

No managed app catalog is configured on this deployment.

) : null} {!loading && catalog.length > 0 && visible.length === 0 && !showFeatured ? (

No apps match your search.

) : null} {visible.length > 0 ? (
{rendered.map((item) => renderCatalogTile(item, item.name, item.logo, { // Avoid duplicate connection-tile-* ids while featured is also shown. tileTestId: !showFeatured, }), )}
) : null} {rendered.length < visible.length ? (
) : null}
{ if (!(event.currentTarget as HTMLDetailsElement).open) { setSourceKind(null); setSourceError(null); setSourceHint(null); setSourceName(""); setSourceUrl(""); setCredential(""); setAuthType("none"); setAuthName("x-api-key"); } }} > Advanced
{catalogFeedEnabled ? ( Search by domain
{ event.preventDefault(); void searchCatalogFeed(); }} > { setCatalogFeedQuery(event.target.value); setCatalogFeedResults([]); setCatalogFeedError(null); setCatalogFeedSearched(false); }} placeholder="github.com" aria-label={t`Integration domain`} />
{catalogFeedError ? (

{catalogFeedError}

) : null} {catalogFeedSearched && !catalogFeedPending && catalogFeedResults.length === 0 ? (

No results

) : null} {catalogFeedResults.map((result) => (
{result.pageUrl ? ( {result.name} ) : ( result.name )}
{result.domain}
{result.description ? (

{result.description}

) : null}
{result.surfaces.map((surface) => { const canAdd = Boolean(surface.source) && (surface.kind === "mcp" || surface.kind === "openapi"); return ( ); })}
))}
) : null}
{sourceError ?

{sourceError}

: null} {sourceKind ? ( {sourceKind === "treg" ? ( Connect Treg ) : sourceKind === "executor" ? ( Connect Executor ) : sourceKind === "mcp" ? ( Add remote MCP server ) : sourceKind === "graphql" ? ( Add GraphQL endpoint ) : ( Import OpenAPI JSON )} setSourceName(event.target.value)} placeholder={t`Display name`} /> {sourceKind !== "treg" ? ( setSourceUrl(event.target.value)} placeholder={ sourceKind === "mcp" ? "https://example.com/mcp" : sourceKind === "executor" ? "https://executor.example/mcp" : sourceKind === "graphql" ? "https://example.com/graphql" : "https://example.com/openapi.json" } /> ) : null} {sourceKind !== "treg" && sourceKind !== "executor" ? ( setAuthType(event.target.value as typeof authType)} > No authentication Bearer token API key header ) : null} {authType === "header" && sourceKind !== "treg" && sourceKind !== "executor" ? ( setAuthName(event.target.value)} placeholder={t`Header name`} /> ) : null} {sourceKind === "treg" || sourceKind === "executor" || authType !== "none" ? ( setCredential(event.target.value)} placeholder={ sourceKind === "treg" ? t`Treg token` : sourceKind === "executor" ? t`Executor token` : t`Credential` } /> ) : null}

Credentials are encrypted and never sent to the model.

{sourceHint ? (

{sourceHint}

) : null}
) : null}
Tool sources
{sources.length === 0 && !sourceKind ? (

No MCP or API tool sources installed yet.

) : null} {sources.map((source) => (
{source.kind === "mcp" ? "M" : source.kind === "graphql" ? "G" : "A"}
{source.name}
{source.kind.toUpperCase()} · {source.source} ·{" "} {source.secretConfigured ? ( credential saved ) : ( no auth )}
))} {onOpenMcp ? ( ) : null}
)}
); if (embedded) { return (
{searchField} {catalogBody}
); } return ( { if (!open) onClose(); }} > Integrations } > {searchField} {catalogBody} ); }