import React from 'react'; import { Image as ImageIcon, Loader2 } from 'lucide-react'; import type { ResourceMetadata } from '@dexto/core'; import { useResourceContent } from './hooks/useResourceContent'; import type { NormalizedResourceItem, ResourceState } from './hooks/useResourceContent'; import { filterAndSortResources } from '../lib/utils.js'; interface ResourceAutocompleteProps { resources: ResourceMetadata[]; query: string; selectedIndex: number; onSelect: (resource: ResourceMetadata) => void; onHoverIndex?: (index: number) => void; loading?: boolean; } export default function ResourceAutocomplete({ resources, query, selectedIndex, onSelect, onHoverIndex, loading, }: ResourceAutocompleteProps) { const filtered = React.useMemo( () => filterAndSortResources(resources, query), [resources, query] ); const imageResourceUris = React.useMemo( () => filtered.filter((r) => (r.mimeType || '').startsWith('image/')).map((r) => r.uri), [filtered] ); const imageResources = useResourceContent(imageResourceUris); const itemRefs = React.useRef([]); React.useEffect(() => { const el = itemRefs.current[selectedIndex]; if (el && el.scrollIntoView) { el.scrollIntoView({ block: 'nearest' }); } }, [selectedIndex, filtered.length]); if (!query && filtered.length === 0 && !loading) { return (
No resources available.
Connect an MCP server or enable internal resources to attach references.
); } // Generate stable IDs for ARIA const getOptionId = (uri: string) => `resource-option-${btoa(uri).replace(/[^a-zA-Z0-9]/g, '')}`; const activeDescendant = filtered[selectedIndex] ? getOptionId(filtered[selectedIndex].uri) : undefined; return loading ? (
Loading resources…
) : filtered.length === 0 ? (
No resources match "{query}"
Tip: @ references only work at start or after spaces
) : ( ); } interface ResourceThumbnailProps { resourceState?: ResourceState; } function ResourceThumbnail({ resourceState }: ResourceThumbnailProps) { const baseClasses = 'w-10 h-10 rounded-md border border-border bg-muted/40 flex items-center justify-center overflow-hidden flex-shrink-0'; if (!resourceState) { return (
); } if (resourceState.status === 'loading') { return (
); } if (resourceState.status === 'error') { return (
); } const imageItem = resourceState.data?.items.find( (item): item is Extract => item.kind === 'image' ); if (!imageItem || !('src' in imageItem)) { return (
); } return (
{imageItem.alt
); }