import { useState } from 'react' /** * PlaceholderPicker - Modal zur Auswahl von Template-Platzhaltern * * Props: * - nodes: Array of workflow nodes (to extract available placeholders) * - onSelect: (placeholderString) => void - Callback when placeholder is selected * - onClose: () => void * * Features: * - Lists all available node outputs * - Copy to clipboard or insert into template * - Kategorisiert nach Node-Typ */ export function PlaceholderPicker({ nodes, onSelect, onClose }) { const [searchQuery, setSearchQuery] = useState('') // Extrahiere verfügbare Platzhalter aus Nodes const placeholders = extractPlaceholders(nodes) // Filtere basierend auf Suchquery const filteredPlaceholders = placeholders.filter(p => p.placeholder.toLowerCase().includes(searchQuery.toLowerCase()) || p.description.toLowerCase().includes(searchQuery.toLowerCase()) ) const handleSelect = (placeholderString) => { onSelect(placeholderString) onClose() // Schließe Modal nach Auswahl } return (
e.stopPropagation()} > {/* Header */}

Platzhalter auswählen

{/* Search */} setSearchQuery(e.target.value)} style={{ width: '100%', padding: '8px 12px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text1)', fontSize: '14px', marginBottom: '16px' }} /> {/* Placeholder List */}
{filteredPlaceholders.length === 0 ? (
{searchQuery ? 'Keine Platzhalter gefunden' : 'Keine Nodes im Workflow'}
) : (
{filteredPlaceholders.map((p, idx) => (
handleSelect(p.placeholder)} style={{ padding: '12px', borderBottom: idx < filteredPlaceholders.length - 1 ? '1px solid var(--border)' : 'none', cursor: 'pointer', transition: 'background 0.2s' }} onMouseEnter={(e) => e.currentTarget.style.background = 'var(--surface2)'} onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'} >
{p.placeholder}
{p.description}
{p.icon}
))}
)}
{/* Footer */}
💡 Syntax: {'{{ node_id.field }}'}
Verfügbare Felder: analysis_core, signal_* (wenn Fragen vorhanden)
) } /** * Extrahiert verfügbare Platzhalter aus Workflow-Nodes */ function extractPlaceholders(nodes) { const placeholders = [] // Globale Platzhalter placeholders.push({ placeholder: '{{ analysis_core }}', description: 'Finale Analyse (AUTO-Modus Fallback)', icon: '🏁', category: 'global' }) // Node-spezifische Platzhalter nodes.forEach(node => { if (node.type === 'end') return // End Node hat keine Outputs const nodeId = node.id const label = node.data.label || nodeId // analysis_core für alle Analysis/Logic/Join Nodes if (node.type === 'analysis' || node.type === 'logic' || node.type === 'join') { placeholders.push({ placeholder: `{{ ${nodeId}.analysis_core }}`, description: `${getNodeIcon(node.type)} ${label} - Hauptausgabe`, icon: getNodeIcon(node.type), category: 'node_outputs' }) } // Signals für Analysis Nodes mit Fragen if (node.type === 'analysis' && node.data.questions && node.data.questions.length > 0) { node.data.questions.forEach(q => { const questionId = q.id || `q${placeholders.length}` placeholders.push({ placeholder: `{{ ${nodeId}.signal_${questionId} }}`, description: `${label} - Signal: ${q.question?.substring(0, 40)}...`, icon: '📊', category: 'signals' }) }) } }) return placeholders } /** * Node-Typ zu Icon */ function getNodeIcon(type) { const icons = { start: '🚀', analysis: '🤖', logic: '⚡', join: '🔀', end: '🏁' } return icons[type] || '📦' }