2026-04-09 16:04:39 +02:00
|
|
|
|
import { useState, useEffect } from 'react'
|
|
|
|
|
|
import { api } from '../../../utils/api'
|
2026-04-09 15:52:19 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* PlaceholderPicker - Modal zur Auswahl von Template-Platzhaltern
|
|
|
|
|
|
*
|
|
|
|
|
|
* Props:
|
2026-04-09 16:04:39 +02:00
|
|
|
|
* - nodes: Array of workflow nodes (to extract workflow-specific placeholders)
|
2026-04-09 15:52:19 +02:00
|
|
|
|
* - onSelect: (placeholderString) => void - Callback when placeholder is selected
|
|
|
|
|
|
* - onClose: () => void
|
|
|
|
|
|
*
|
|
|
|
|
|
* Features:
|
2026-04-09 16:04:39 +02:00
|
|
|
|
* - Lädt registrierte Platzhalter vom Backend (~120+)
|
|
|
|
|
|
* - Extrahiert Workflow-spezifische Node-Outputs
|
|
|
|
|
|
* - Zeigt Node-Namen (nicht nur IDs)
|
|
|
|
|
|
* - Kategorisiert: System + Workflow
|
|
|
|
|
|
* - Suchfunktion über alle Kategorien
|
2026-04-09 15:52:19 +02:00
|
|
|
|
*/
|
|
|
|
|
|
export function PlaceholderPicker({ nodes, onSelect, onClose }) {
|
|
|
|
|
|
const [searchQuery, setSearchQuery] = useState('')
|
2026-04-09 16:04:39 +02:00
|
|
|
|
const [systemPlaceholders, setSystemPlaceholders] = useState([])
|
|
|
|
|
|
const [loading, setLoading] = useState(true)
|
2026-04-09 16:12:42 +02:00
|
|
|
|
const [loadError, setLoadError] = useState(null)
|
2026-04-09 15:52:19 +02:00
|
|
|
|
|
2026-04-09 16:04:39 +02:00
|
|
|
|
// Lade Backend-Platzhalter beim Mount
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
async function loadPlaceholders() {
|
|
|
|
|
|
try {
|
2026-04-09 16:12:42 +02:00
|
|
|
|
console.log('🔄 Loading placeholders from backend...')
|
2026-04-09 16:04:39 +02:00
|
|
|
|
const catalog = await api.listPlaceholders()
|
2026-04-09 16:12:42 +02:00
|
|
|
|
console.log('✅ Catalog received:', catalog)
|
|
|
|
|
|
console.log('📊 Catalog keys:', Object.keys(catalog))
|
|
|
|
|
|
|
2026-04-09 16:04:39 +02:00
|
|
|
|
// Konvertiere Katalog zu Flat-Liste
|
|
|
|
|
|
const flattened = []
|
|
|
|
|
|
Object.entries(catalog).forEach(([category, items]) => {
|
2026-04-09 16:12:42 +02:00
|
|
|
|
console.log(`📁 Category "${category}": ${items?.length || 0} items`)
|
|
|
|
|
|
if (!Array.isArray(items)) {
|
|
|
|
|
|
console.warn(`⚠️ Category "${category}" items is not an array:`, items)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-04-09 16:04:39 +02:00
|
|
|
|
items.forEach(item => {
|
|
|
|
|
|
flattened.push({
|
|
|
|
|
|
placeholder: `{{ ${item.key.trim()} }}`,
|
|
|
|
|
|
description: item.description || 'Keine Beschreibung',
|
|
|
|
|
|
example: item.example || '',
|
|
|
|
|
|
category: category,
|
|
|
|
|
|
icon: getCategoryIcon(category)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2026-04-09 16:12:42 +02:00
|
|
|
|
console.log(`✅ Loaded ${flattened.length} system placeholders`)
|
2026-04-09 16:04:39 +02:00
|
|
|
|
setSystemPlaceholders(flattened)
|
|
|
|
|
|
} catch (e) {
|
2026-04-09 16:12:42 +02:00
|
|
|
|
console.error('❌ Failed to load placeholders:', e)
|
|
|
|
|
|
setLoadError(e.message)
|
2026-04-09 16:04:39 +02:00
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
loadPlaceholders()
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
|
|
// Extrahiere Workflow-spezifische Platzhalter
|
|
|
|
|
|
const workflowPlaceholders = extractWorkflowPlaceholders(nodes)
|
|
|
|
|
|
|
|
|
|
|
|
// Kombiniere beide Listen
|
|
|
|
|
|
const allPlaceholders = [
|
|
|
|
|
|
...workflowPlaceholders,
|
|
|
|
|
|
...systemPlaceholders
|
|
|
|
|
|
]
|
2026-04-09 15:52:19 +02:00
|
|
|
|
|
|
|
|
|
|
// Filtere basierend auf Suchquery
|
2026-04-09 16:04:39 +02:00
|
|
|
|
const filteredPlaceholders = allPlaceholders.filter(p =>
|
2026-04-09 15:52:19 +02:00
|
|
|
|
p.placeholder.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
2026-04-09 16:04:39 +02:00
|
|
|
|
p.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
|
|
|
|
(p.category && p.category.toLowerCase().includes(searchQuery.toLowerCase()))
|
2026-04-09 15:52:19 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-04-09 16:04:39 +02:00
|
|
|
|
// Gruppiere nach Kategorie
|
|
|
|
|
|
const grouped = {}
|
|
|
|
|
|
filteredPlaceholders.forEach(p => {
|
|
|
|
|
|
const cat = p.category || 'Sonstige'
|
|
|
|
|
|
if (!grouped[cat]) grouped[cat] = []
|
|
|
|
|
|
grouped[cat].push(p)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-04-09 15:52:19 +02:00
|
|
|
|
const handleSelect = (placeholderString) => {
|
|
|
|
|
|
onSelect(placeholderString)
|
2026-04-09 16:04:39 +02:00
|
|
|
|
onClose()
|
2026-04-09 15:52:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
position: 'fixed',
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
left: 0,
|
|
|
|
|
|
right: 0,
|
|
|
|
|
|
bottom: 0,
|
|
|
|
|
|
background: 'rgba(0,0,0,0.5)',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
zIndex: 2000
|
|
|
|
|
|
}}
|
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
background: 'var(--surface)',
|
|
|
|
|
|
borderRadius: '12px',
|
|
|
|
|
|
padding: '24px',
|
2026-04-09 16:04:39 +02:00
|
|
|
|
maxWidth: '700px',
|
2026-04-09 15:52:19 +02:00
|
|
|
|
width: '90%',
|
|
|
|
|
|
maxHeight: '80vh',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
|
boxShadow: '0 4px 16px rgba(0,0,0,0.2)'
|
|
|
|
|
|
}}
|
|
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
|
>
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
marginBottom: '16px'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-04-09 16:04:39 +02:00
|
|
|
|
<h2 style={{ margin: 0, fontSize: '18px' }}>
|
|
|
|
|
|
Platzhalter auswählen
|
|
|
|
|
|
{loading && <span style={{ fontSize: '14px', color: 'var(--text3)', marginLeft: '12px' }}>Lädt...</span>}
|
|
|
|
|
|
</h2>
|
2026-04-09 15:52:19 +02:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
background: 'none',
|
|
|
|
|
|
border: 'none',
|
|
|
|
|
|
fontSize: '24px',
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
color: 'var(--text3)',
|
|
|
|
|
|
padding: '4px 8px',
|
|
|
|
|
|
lineHeight: 1
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
×
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Search */}
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
2026-04-09 16:04:39 +02:00
|
|
|
|
placeholder="Suche nach Platzhalter, Kategorie oder Beschreibung..."
|
2026-04-09 15:52:19 +02:00
|
|
|
|
value={searchQuery}
|
|
|
|
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
2026-04-09 16:04:39 +02:00
|
|
|
|
autoFocus
|
2026-04-09 15:52:19 +02:00
|
|
|
|
style={{
|
|
|
|
|
|
width: '100%',
|
|
|
|
|
|
padding: '8px 12px',
|
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
|
border: '1px solid var(--border)',
|
|
|
|
|
|
background: 'var(--bg)',
|
|
|
|
|
|
color: 'var(--text1)',
|
|
|
|
|
|
fontSize: '14px',
|
|
|
|
|
|
marginBottom: '16px'
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
2026-04-09 16:04:39 +02:00
|
|
|
|
{/* Stats */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
|
color: 'var(--text3)',
|
|
|
|
|
|
marginBottom: '12px'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{filteredPlaceholders.length} Platzhalter gefunden
|
|
|
|
|
|
{!searchQuery && ` (${workflowPlaceholders.length} Workflow, ${systemPlaceholders.length} System)`}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Placeholder List (Grouped) */}
|
2026-04-09 15:52:19 +02:00
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
overflowY: 'auto',
|
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
|
border: '1px solid var(--border)'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-04-09 16:04:39 +02:00
|
|
|
|
{loading ? (
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
padding: '24px',
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
color: 'var(--text3)',
|
|
|
|
|
|
fontSize: '14px'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
Lade Platzhalter...
|
|
|
|
|
|
</div>
|
2026-04-09 16:12:42 +02:00
|
|
|
|
) : loadError ? (
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
padding: '24px',
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
color: 'var(--danger)',
|
|
|
|
|
|
fontSize: '14px'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
❌ Fehler beim Laden: {loadError}
|
|
|
|
|
|
<div style={{ marginTop: '12px', fontSize: '12px', color: 'var(--text3)' }}>
|
|
|
|
|
|
Workflow-Platzhalter sind trotzdem verfügbar.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-09 16:04:39 +02:00
|
|
|
|
) : Object.keys(grouped).length === 0 ? (
|
2026-04-09 15:52:19 +02:00
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
padding: '24px',
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
color: 'var(--text3)',
|
|
|
|
|
|
fontSize: '14px'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-04-09 16:04:39 +02:00
|
|
|
|
{searchQuery ? 'Keine Platzhalter gefunden' : 'Keine Platzhalter verfügbar'}
|
2026-04-09 15:52:19 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
2026-04-09 16:04:39 +02:00
|
|
|
|
<div>
|
|
|
|
|
|
{Object.entries(grouped).map(([category, items], catIdx) => (
|
|
|
|
|
|
<div key={catIdx}>
|
|
|
|
|
|
{/* Category Header */}
|
2026-04-09 15:52:19 +02:00
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
2026-04-09 16:04:39 +02:00
|
|
|
|
padding: '8px 12px',
|
|
|
|
|
|
background: 'var(--surface2)',
|
|
|
|
|
|
borderBottom: '1px solid var(--border)',
|
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
|
color: 'var(--text2)',
|
|
|
|
|
|
position: 'sticky',
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
zIndex: 1
|
2026-04-09 15:52:19 +02:00
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-04-09 16:04:39 +02:00
|
|
|
|
{category} ({items.length})
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Category Items */}
|
|
|
|
|
|
{items.map((p, idx) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={`${catIdx}-${idx}`}
|
|
|
|
|
|
onClick={() => handleSelect(p.placeholder)}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
padding: '12px',
|
|
|
|
|
|
borderBottom: '1px solid var(--border)',
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
transition: 'background 0.2s'
|
|
|
|
|
|
}}
|
|
|
|
|
|
onMouseEnter={(e) => e.currentTarget.style.background = 'var(--surface2)'}
|
|
|
|
|
|
onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
|
|
|
|
|
|
>
|
2026-04-09 15:52:19 +02:00
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
2026-04-09 16:04:39 +02:00
|
|
|
|
display: 'flex',
|
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
|
alignItems: 'flex-start',
|
|
|
|
|
|
gap: '12px'
|
2026-04-09 15:52:19 +02:00
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-04-09 16:04:39 +02:00
|
|
|
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
fontFamily: 'monospace',
|
|
|
|
|
|
fontSize: '13px',
|
|
|
|
|
|
color: 'var(--accent)',
|
|
|
|
|
|
marginBottom: '4px',
|
|
|
|
|
|
wordBreak: 'break-all'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{p.placeholder}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
|
color: 'var(--text2)',
|
|
|
|
|
|
marginBottom: p.example ? '4px' : '0'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{p.description}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{p.example && (
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
fontSize: '11px',
|
|
|
|
|
|
color: 'var(--text3)',
|
|
|
|
|
|
fontStyle: 'italic'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
Beispiel: {p.example}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
fontSize: '20px',
|
|
|
|
|
|
flexShrink: 0,
|
|
|
|
|
|
color: 'var(--text3)'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{p.icon}
|
|
|
|
|
|
</div>
|
2026-04-09 15:52:19 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-09 16:04:39 +02:00
|
|
|
|
))}
|
2026-04-09 15:52:19 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Footer */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
marginTop: '16px',
|
|
|
|
|
|
padding: '12px',
|
|
|
|
|
|
background: 'var(--bg)',
|
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
|
color: 'var(--text3)'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-04-09 16:04:39 +02:00
|
|
|
|
💡 <strong>Syntax:</strong> <code style={{ background: 'var(--surface2)', padding: '2px 6px', borderRadius: '4px' }}>{'{{ placeholder_name }}'}</code>
|
2026-04-09 15:52:19 +02:00
|
|
|
|
<br />
|
2026-04-09 16:04:39 +02:00
|
|
|
|
Klicke auf einen Platzhalter um ihn einzufügen
|
2026-04-09 15:52:19 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-09 16:04:39 +02:00
|
|
|
|
* Extrahiert Workflow-spezifische Platzhalter aus Nodes
|
2026-04-09 15:52:19 +02:00
|
|
|
|
*/
|
2026-04-09 16:04:39 +02:00
|
|
|
|
function extractWorkflowPlaceholders(nodes) {
|
2026-04-09 15:52:19 +02:00
|
|
|
|
const placeholders = []
|
|
|
|
|
|
|
2026-04-09 16:12:42 +02:00
|
|
|
|
console.log('🔍 Extracting workflow placeholders from nodes:', nodes)
|
|
|
|
|
|
|
2026-04-09 15:52:19 +02:00
|
|
|
|
nodes.forEach(node => {
|
|
|
|
|
|
if (node.type === 'end') return // End Node hat keine Outputs
|
|
|
|
|
|
|
|
|
|
|
|
const nodeId = node.id
|
2026-04-09 16:12:42 +02:00
|
|
|
|
const nodeLabel = node.data?.label || nodeId
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`📦 Node ${nodeId}:`, {
|
|
|
|
|
|
type: node.type,
|
|
|
|
|
|
label: node.data?.label,
|
|
|
|
|
|
nodeLabel: nodeLabel,
|
|
|
|
|
|
data: node.data
|
|
|
|
|
|
})
|
2026-04-09 15:52:19 +02:00
|
|
|
|
|
|
|
|
|
|
// analysis_core für alle Analysis/Logic/Join Nodes
|
|
|
|
|
|
if (node.type === 'analysis' || node.type === 'logic' || node.type === 'join') {
|
2026-04-09 16:12:42 +02:00
|
|
|
|
const desc = `${nodeLabel} (${nodeId}) - Hauptausgabe`
|
|
|
|
|
|
console.log(` ➕ Adding placeholder: {{ ${nodeId}.analysis_core }} → "${desc}"`)
|
2026-04-09 15:52:19 +02:00
|
|
|
|
placeholders.push({
|
|
|
|
|
|
placeholder: `{{ ${nodeId}.analysis_core }}`,
|
2026-04-09 16:12:42 +02:00
|
|
|
|
description: desc,
|
2026-04-09 15:52:19 +02:00
|
|
|
|
icon: getNodeIcon(node.type),
|
2026-04-09 16:04:39 +02:00
|
|
|
|
category: 'Workflow - Node Outputs'
|
2026-04-09 15:52:19 +02:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-09 20:49:45 +02:00
|
|
|
|
// Signals und Fragen für Analysis Nodes
|
2026-04-09 15:52:19 +02:00
|
|
|
|
if (node.type === 'analysis' && node.data.questions && node.data.questions.length > 0) {
|
2026-04-09 16:04:39 +02:00
|
|
|
|
node.data.questions.forEach((q, qIdx) => {
|
2026-04-09 20:54:43 +02:00
|
|
|
|
const questionId = q.id || `q${qIdx + 1}`
|
2026-04-09 21:01:24 +02:00
|
|
|
|
const questionType = q.type || 'unknown'
|
2026-04-09 16:04:39 +02:00
|
|
|
|
const questionText = q.question || `Frage ${qIdx + 1}`
|
2026-04-09 20:49:45 +02:00
|
|
|
|
|
2026-04-09 21:01:24 +02:00
|
|
|
|
// Signal-Platzhalter (Antwort) - VERWENDET ID für Eindeutigkeit!
|
2026-04-09 15:52:19 +02:00
|
|
|
|
placeholders.push({
|
2026-04-09 21:01:24 +02:00
|
|
|
|
placeholder: `{{ ${nodeId}.signal_${questionId} }}`,
|
|
|
|
|
|
description: `${nodeLabel} - ${questionId} (${questionType}): ${questionText.substring(0, 45)}${questionText.length > 45 ? '...' : ''}`,
|
2026-04-09 15:52:19 +02:00
|
|
|
|
icon: '📊',
|
2026-04-09 16:04:39 +02:00
|
|
|
|
category: 'Workflow - Signals'
|
2026-04-09 15:52:19 +02:00
|
|
|
|
})
|
2026-04-09 20:49:45 +02:00
|
|
|
|
|
|
|
|
|
|
// Frage-Text-Platzhalter
|
|
|
|
|
|
placeholders.push({
|
2026-04-09 21:01:24 +02:00
|
|
|
|
placeholder: `{{ ${nodeId}.question_${questionId} }}`,
|
|
|
|
|
|
description: `${nodeLabel} - ${questionId} (${questionType}): ${questionText.substring(0, 45)}${questionText.length > 45 ? '...' : ''}`,
|
2026-04-09 20:49:45 +02:00
|
|
|
|
icon: '❓',
|
|
|
|
|
|
category: 'Workflow - Questions'
|
|
|
|
|
|
})
|
2026-04-09 15:52:19 +02:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return placeholders
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Node-Typ zu Icon
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getNodeIcon(type) {
|
|
|
|
|
|
const icons = {
|
|
|
|
|
|
start: '🚀',
|
|
|
|
|
|
analysis: '🤖',
|
|
|
|
|
|
logic: '⚡',
|
|
|
|
|
|
join: '🔀',
|
|
|
|
|
|
end: '🏁'
|
|
|
|
|
|
}
|
|
|
|
|
|
return icons[type] || '📦'
|
|
|
|
|
|
}
|
2026-04-09 16:04:39 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Kategorie zu Icon
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getCategoryIcon(category) {
|
|
|
|
|
|
const icons = {
|
|
|
|
|
|
'Profil': '👤',
|
|
|
|
|
|
'Körper': '💪',
|
|
|
|
|
|
'Ernährung': '🍎',
|
|
|
|
|
|
'Training': '🏃',
|
|
|
|
|
|
'Schlaf': '😴',
|
|
|
|
|
|
'Vitalwerte': '❤️',
|
|
|
|
|
|
'Ziele': '🎯',
|
|
|
|
|
|
'Scores': '📊',
|
|
|
|
|
|
'Korrelationen': '📈'
|
|
|
|
|
|
}
|
|
|
|
|
|
return icons[category] || '📦'
|
|
|
|
|
|
}
|