/** * SourceDiscoveryWidget - Displays orphan sources and allows widget generation * * This widget enables the reverse flow: Source → Widget * Shows data sources that don't have matching widgets and offers to generate them. */ import React, { useState } from 'react'; import { Database, Zap, Globe, AlertTriangle, Plus, RefreshCw, Check, Loader2, ChevronRight, Sparkles, Link2 } from 'lucide-react'; import { useSourceDiscovery, useGeneratedWidgets } from '@/services/SourceWidgetDiscovery'; import { cn } from '@/lib/utils'; // Icon mapping for source types const sourceTypeIcons: Record = { database: , 'mcp-tool': , file: , 'email-adapter': , external: , osint: , threatIntel: , }; export default function SourceDiscoveryWidget() { const { sources, orphanSources, suggestedWidgets, isLoading, refresh, generateWidget } = useSourceDiscovery(); const { generatedWidgets } = useGeneratedWidgets(); const [generatingFor, setGeneratingFor] = useState(null); const [activeTab, setActiveTab] = useState<'orphans' | 'all' | 'generated'>('orphans'); const handleGenerateWidget = async (sourceId: string, suggestedName: string) => { setGeneratingFor(sourceId); try { await generateWidget(sourceId, suggestedName); } finally { setGeneratingFor(null); } }; return (
{/* Header */}
Source Discovery
{sources.length} kilder • {orphanSources.length} uden widget
{/* Tabs */}
{[ { id: 'orphans', label: 'Orphan Kilder', count: orphanSources.length }, { id: 'all', label: 'Alle Kilder', count: sources.length }, { id: 'generated', label: 'Genererede', count: generatedWidgets.length }, ].map(tab => ( ))}
{/* Content */}
{isLoading ? (
) : activeTab === 'orphans' ? ( orphanSources.length === 0 ? (

Alle kilder har widgets!

) : ( orphanSources.map(source => { const suggested = suggestedWidgets.find(s => s.forSource === source.id); return (
{sourceTypeIcons[source.type] || }

{source.name}

{source.type}

{/* Capabilities */}
{source.capabilities.slice(0, 4).map((cap, i) => ( {cap} ))} {source.capabilities.length > 4 && ( +{source.capabilities.length - 4} flere )}
{/* Suggested widget info */} {suggested && (
Foreslået: {suggested.suggestedName}
)}
); }) ) ) : activeTab === 'all' ? ( sources.map(source => (
{sourceTypeIcons[source.type] || }

{source.name}

{source.type} {source.estimatedLatency}ms {source.recommendedWidgets.length > 0 && ( <> {source.recommendedWidgets[0]} )}
{source.hasWidget ? ( ) : ( Ingen widget )}
)) ) : ( /* Generated widgets tab */ generatedWidgets.length === 0 ? (

Ingen genererede widgets endnu

) : ( generatedWidgets.map((widget, i) => (

{widget.name}

For: {widget.dataSource} • Template: {widget.template}

{widget.capabilities.slice(0, 3).map((cap, j) => ( {cap} ))}
)) ) )}
{/* Footer */}

Autonomt system lærer fra kilde↔widget koblinger

); }