import { Suspense, useEffect, useMemo, useState } from "react"; import { useNavigate } from "@tanstack/react-router"; import { useAtomValue, useAtomSet, useAtomRefresh, Result } from "@effect-atom/atom-react"; import { sourceToolsAtom, sourcesAtom, sourceAtom, removeSource, refreshSource, } from "../api/atoms"; import { sourceWriteKeys } from "../api/reactivity-keys"; import { ToolTree } from "../components/tool-tree"; import { ToolDetail, ToolDetailEmpty } from "../components/tool-detail"; import type { ToolSummary } from "../components/tool-tree"; import { useScope } from "../hooks/use-scope"; import type { SourcePlugin } from "../plugins/source-plugin"; import { Button } from "../components/button"; import { Badge } from "../components/badge"; import { Skeleton } from "../components/skeleton"; export function SourceDetailPage(props: { namespace: string; sourcePlugins?: readonly SourcePlugin[]; }) { const { namespace, sourcePlugins } = props; const scopeId = useScope(); const source = useAtomValue(sourceAtom(namespace, scopeId)); const tools = useAtomValue(sourceToolsAtom(namespace, scopeId)); const refreshSources = useAtomRefresh(sourcesAtom(scopeId)); const refreshTools = useAtomRefresh(sourceToolsAtom(namespace, scopeId)); const doRemove = useAtomSet(removeSource, { mode: "promise" }); const doRefresh = useAtomSet(refreshSource, { mode: "promise" }); const navigate = useNavigate(); // HMR: refresh source tools when the backend is hot-reloaded useEffect(() => { if (!import.meta.hot) return; const refresh = () => { refreshTools(); refreshSources(); }; import.meta.hot.on("executor:backend-updated", refresh); return () => { import.meta.hot?.off("executor:backend-updated", refresh); }; }, [refreshTools, refreshSources]); const [selectedToolId, setSelectedToolId] = useState(null); const [confirmDelete, setConfirmDelete] = useState(false); const [deleting, setDeleting] = useState(false); const [refreshing, setRefreshing] = useState(false); const [editing, setEditing] = useState(false); const sourceData = Result.isSuccess(source) ? source.value : null; const canRefresh = sourceData ? (sourceData.canRefresh ?? true) : false; const canRemove = sourceData ? (sourceData.canRemove ?? true) : false; const canEdit = sourceData ? (sourceData.canEdit ?? false) : false; // Find the plugin edit component based on source kind const editPlugin = useMemo(() => { if (!sourceData || !sourcePlugins) return null; return sourcePlugins.find((p) => p.key === sourceData.kind) ?? null; }, [sourceData, sourcePlugins]); const sourceTools: ToolSummary[] = useMemo(() => { if (!Result.isSuccess(tools)) return []; return tools.value.map((t) => ({ id: t.id, name: t.name, pluginKey: t.pluginId, description: t.description, })); }, [tools]); const selectedTool = useMemo( () => sourceTools.find((t) => t.id === selectedToolId) ?? null, [sourceTools, selectedToolId], ); const handleDelete = async () => { setDeleting(true); try { await doRemove({ path: { scopeId, sourceId: namespace }, reactivityKeys: sourceWriteKeys, }); void navigate({ to: "/" }); } catch { setDeleting(false); setConfirmDelete(false); } }; const handleRefresh = async () => { setRefreshing(true); try { await doRefresh({ path: { scopeId, sourceId: namespace }, reactivityKeys: sourceWriteKeys, }); } finally { setRefreshing(false); } }; const handleEditSave = () => { setEditing(false); }; return (
{/* Header bar */}

{sourceData?.name ?? namespace}

{sourceData?.runtime && ( built-in )} {sourceData?.kind ?? "source"} {Result.isSuccess(tools) && !editing && ( {sourceTools.length} {sourceTools.length === 1 ? "tool" : "tools"} )}
{editPlugin?.signIn && !editing && ( )} {canEdit && editPlugin && !editing && ( )} {editing && ( )} {canRefresh && !editing && ( )} {canRemove && !editing && (confirmDelete ? (
Confirm?
) : ( ))}
{/* Edit view */} {editing && editPlugin ? (
}>
) : ( /* Content -- split pane */ Result.match(tools, { onInitial: () => , onFailure: () =>
Failed to load tools
, onSuccess: () => (
{/* Left: tool tree */}
{/* Right: tool detail */}
{selectedTool ? ( ) : ( 0} /> )}
), }) )}
); } function SourceDetailSkeleton() { return (
{/* Left: tool tree skeleton */}
{Array.from({ length: 8 }).map((_, i) => (
))}
{/* Right: tool detail skeleton */}
); } function EditFormSkeleton() { return (
); }