import { Suspense, useCallback, useMemo, useState } from "react"; import { Link, useNavigate } from "@tanstack/react-router"; import { useAtomSet, useAtomValue } from "@effect/atom-react"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import * as Exit from "effect/Exit"; import { PlusIcon } from "lucide-react"; import type { SourceDetectionResult } from "@executor-js/sdk"; import { useSourcePlugins, type SourcePlugin, type SourcePreset } from "@executor-js/sdk/client"; import { detectSource, sourcesOptimisticAtom } from "../api/atoms"; import { useScope } from "../hooks/use-scope"; import { McpInstallCard } from "../components/mcp-install-card"; import { Button } from "../components/button"; import { Badge } from "../components/badge"; import { Input } from "../components/input"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "../components/dialog"; import { CardStack, CardStackContent, CardStackEntry, CardStackEntryActions, CardStackEntryContent, CardStackEntryDescription, CardStackEntryMedia, CardStackEntryTitle, } from "../components/card-stack"; import { SourceFavicon } from "../components/source-favicon"; import { Skeleton } from "../components/skeleton"; const KIND_TO_PLUGIN_KEY: Record = { openapi: "openapi", mcp: "mcp", graphql: "graphql", googleDiscovery: "googleDiscovery", }; const detectionRank: Record = { high: 3, medium: 2, low: 1, }; const bestDetection = ( results: readonly SourceDetectionResult[], ): SourceDetectionResult | undefined => [...results].sort((a, b) => detectionRank[b.confidence] - detectionRank[a.confidence])[0]; // --------------------------------------------------------------------------- // Page // --------------------------------------------------------------------------- export function SourcesPage() { const scopeId = useScope(); const sources = useAtomValue(sourcesOptimisticAtom(scopeId)); const [connectOpen, setConnectOpen] = useState(false); return (

Sources

Tool providers available in this workspace.

{AsyncResult.match(sources, { onInitial: () => , onFailure: () =>

Failed to load sources

, onSuccess: ({ value }) => { const connectedSources = ( value as Array<{ readonly id: string; readonly name: string; readonly kind: string; readonly url?: string; readonly runtime?: boolean; }> ).filter((source) => !source.runtime); if (connectedSources.length === 0) { return setConnectOpen(true)} />; } return (
); }, })}
); } // --------------------------------------------------------------------------- // Connect dialog — URL detection + manual plugin chooser + presets // --------------------------------------------------------------------------- // Heuristic: the input either looks like a URL (auto-detect) or a free-text // search query (filter the preset list). Anything with a scheme, slash, or // host-with-TLD is treated as a URL; everything else is search. const looksLikeUrl = (raw: string): boolean => { const v = raw.trim(); if (v.length === 0) return false; if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(v)) return true; if (v.includes("/")) return true; if (/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}(?::\d+)?$/i.test(v)) return true; return false; }; function ConnectDialog(props: { open: boolean; onOpenChange: (open: boolean) => void }) { const sourcePlugins = useSourcePlugins(); const scopeId = useScope(); const doDetect = useAtomSet(detectSource, { mode: "promiseExit" }); const navigate = useNavigate(); const [query, setQuery] = useState(""); const [detecting, setDetecting] = useState(false); const [error, setError] = useState(null); const isUrl = looksLikeUrl(query); const presetSearch = isUrl ? "" : query; const closeAndReset = useCallback(() => { setQuery(""); setError(null); setDetecting(false); props.onOpenChange(false); }, [props]); const handleDetect = useCallback(async () => { const trimmed = query.trim(); if (!trimmed) return; setDetecting(true); setError(null); const exit = await doDetect({ params: { scopeId }, payload: { url: trimmed }, }); if (Exit.isFailure(exit)) { setError("Detection failed. Try adding a source manually."); setDetecting(false); return; } const results = exit.value; if (results.length === 0) { setError("Could not detect a source type from this URL. Try adding manually."); setDetecting(false); return; } const detected = bestDetection(results); if (!detected) { setError("Could not detect a source type from this URL. Try adding manually."); setDetecting(false); return; } const pluginKey = KIND_TO_PLUGIN_KEY[detected.kind]; if (pluginKey) { closeAndReset(); void navigate({ to: "/sources/add/$pluginKey", params: { pluginKey }, search: { url: trimmed, namespace: detected.namespace }, }); } else { setError(`Detected source type "${detected.kind}" but no plugin is available for it.`); setDetecting(false); } }, [query, doDetect, navigate, scopeId, closeAndReset]); return ( { if (!open) closeAndReset(); else props.onOpenChange(open); }} > Connect a source Search the preset library, or paste a URL to auto-detect.
{ setQuery((e.target as HTMLInputElement).value); setError(null); }} onKeyDown={(e) => { if (e.key === "Enter" && isUrl) void handleDetect(); }} placeholder="Search or paste a URL…" disabled={detecting} className="flex-1" /> {isUrl && ( )}
{error &&

{error}

}

Or add manually

{sourcePlugins.map((p) => ( {p.label} ))}
); } // --------------------------------------------------------------------------- // Empty state // --------------------------------------------------------------------------- function EmptySources(props: { onConnect: () => void }) { return (

No sources yet

Connect a source to start curating tools.

); } // --------------------------------------------------------------------------- // Preset grid (for inside the Connect dialog) // --------------------------------------------------------------------------- type PresetEntry = { preset: SourcePreset; pluginKey: string; pluginLabel: string; }; function PresetGrid(props: { plugins: readonly SourcePlugin[]; onPick: () => void; /** Controlled filter query forwarded from the dialog's unified * search/URL input. Empty string disables filtering. */ searchQuery?: string; }) { const allPresets = useMemo(() => { const entries: PresetEntry[] = []; for (const plugin of props.plugins) { for (const preset of plugin.presets ?? []) { entries.push({ preset, pluginKey: plugin.key, pluginLabel: plugin.label, }); } } return entries; }, [props.plugins]); const filtered = useMemo(() => { const q = (props.searchQuery ?? "").trim().toLowerCase(); if (q.length === 0) return allPresets; return allPresets.filter(({ preset, pluginLabel }) => { const corpus = `${preset.name} ${preset.summary ?? ""} ${pluginLabel}`.toLowerCase(); return corpus.includes(q); }); }, [allPresets, props.searchQuery]); if (allPresets.length === 0) return null; return (

Popular sources

{/* Fixed height keeps the dialog stable as the user filters; the * inner area scrolls when the list overflows and shows an empty * state when no presets match. */} {filtered.length === 0 ? (

No matching presets

Paste a URL above to auto-detect, or pick a source type manually.

) : ( filtered.map(({ preset, pluginKey, pluginLabel }) => { const search: Record = { preset: preset.id }; if (preset.url) search.url = preset.url; return ( {preset.icon ? ( ) : ( )} {preset.name} {preset.summary} {pluginLabel} ); }) )}
); } // --------------------------------------------------------------------------- // Source grid — flat list of connected sources, click-through to detail // --------------------------------------------------------------------------- function SourceGrid(props: { sources: readonly { id: string; name: string; kind: string; url?: string; runtime?: boolean; }[]; }) { const sourcePlugins = useSourcePlugins(); const pluginByKind = useMemo(() => { const out = new Map(); for (const p of sourcePlugins) out.set(p.key, p); return out; }, [sourcePlugins]); return ( {props.sources.map((s) => { const pluginKey = KIND_TO_PLUGIN_KEY[s.kind] ?? s.kind; const plugin = pluginByKind.get(pluginKey); const SummaryComponent = plugin?.summary; return ( {s.name} {s.id} {SummaryComponent && ( )} {s.runtime && built-in} {s.kind} ); })} ); } // --------------------------------------------------------------------------- // Loading skeleton // --------------------------------------------------------------------------- function SourcesGridSkeleton() { return ( {Array.from({ length: 4 }).map((_, i) => (
))}
); }