fix: Inline Prompts - UX-Verbesserungen
Some checks failed
Deploy Development / deploy (push) Failing after 39s
Build Test / pytest-backend (push) Successful in 3s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Failing after 11s

Problem 1: Selbst-Referenzierung verhindern
- PlaceholderPicker erhält currentNodeId prop
- Node kann sich nicht mehr selbst in Placeholders sehen
- extractWorkflowPlaceholders() filtert aktuellen Node aus

Problem 2: Radio-Button State-Management
- IIFE mit Helper-Funktion für Mode-Bestimmung
- isInlineMode/isReferenceMode basierend auf data.inline_template
- Korrekte Conditional Rendering Logic
- Beim Wechsel Reference→Inline bleibt prompt_slug erhalten
- Beim Wechsel Inline→Reference bleibt inline_template erhalten

Problem 3: Layout-Breite optimiert
- Sidebar: 250px → 220px (schmaler)
- Config Panel: 400px → 520px (breiter für bessere Lesbarkeit)
- Responsive: Config Panel bei <1200px: 450px statt 350px

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-04-11 08:58:46 +02:00
parent 65500c899b
commit 8f6d60681e
3 changed files with 111 additions and 95 deletions

View File

@ -6,17 +6,19 @@ import { api } from '../../../utils/api'
* *
* Props: * Props:
* - nodes: Array of workflow nodes (to extract workflow-specific placeholders) * - nodes: Array of workflow nodes (to extract workflow-specific placeholders)
* - currentNodeId: ID des aktuellen Nodes (wird aus Placeholders ausgeschlossen)
* - onSelect: (placeholderString) => void - Callback when placeholder is selected * - onSelect: (placeholderString) => void - Callback when placeholder is selected
* - onClose: () => void * - onClose: () => void
* *
* Features: * Features:
* - Lädt registrierte Platzhalter vom Backend (~120+) * - Lädt registrierte Platzhalter vom Backend (~120+)
* - Extrahiert Workflow-spezifische Node-Outputs * - Extrahiert Workflow-spezifische Node-Outputs
* - Filtert Selbst-Referenzierung (Node kann sich nicht selbst referenzieren)
* - Zeigt Node-Namen (nicht nur IDs) * - Zeigt Node-Namen (nicht nur IDs)
* - Kategorisiert: System + Workflow * - Kategorisiert: System + Workflow
* - Suchfunktion über alle Kategorien * - Suchfunktion über alle Kategorien
*/ */
export function PlaceholderPicker({ nodes, onSelect, onClose }) { export function PlaceholderPicker({ nodes, currentNodeId, onSelect, onClose }) {
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
const [systemPlaceholders, setSystemPlaceholders] = useState([]) const [systemPlaceholders, setSystemPlaceholders] = useState([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
@ -61,8 +63,8 @@ export function PlaceholderPicker({ nodes, onSelect, onClose }) {
loadPlaceholders() loadPlaceholders()
}, []) }, [])
// Extrahiere Workflow-spezifische Platzhalter // Extrahiere Workflow-spezifische Platzhalter (ohne aktuellen Node)
const workflowPlaceholders = extractWorkflowPlaceholders(nodes) const workflowPlaceholders = extractWorkflowPlaceholders(nodes, currentNodeId)
// Kombiniere beide Listen // Kombiniere beide Listen
const allPlaceholders = [ const allPlaceholders = [
@ -341,14 +343,19 @@ export function PlaceholderPicker({ nodes, onSelect, onClose }) {
/** /**
* Extrahiert Workflow-spezifische Platzhalter aus Nodes * Extrahiert Workflow-spezifische Platzhalter aus Nodes
*
* @param {Array} nodes - Alle Workflow-Nodes
* @param {string} currentNodeId - ID des aktuellen Nodes (wird ausgeschlossen)
*/ */
function extractWorkflowPlaceholders(nodes) { function extractWorkflowPlaceholders(nodes, currentNodeId) {
const placeholders = [] const placeholders = []
console.log('🔍 Extracting workflow placeholders from nodes:', nodes) console.log('🔍 Extracting workflow placeholders from nodes:', nodes)
console.log('🚫 Excluding current node:', currentNodeId)
nodes.forEach(node => { nodes.forEach(node => {
if (node.type === 'end') return // End Node hat keine Outputs if (node.type === 'end') return // End Node hat keine Outputs
if (node.id === currentNodeId) return // Selbst-Referenzierung verhindern
const nodeId = node.id const nodeId = node.id
const nodeLabel = node.data?.label || nodeId const nodeLabel = node.data?.label || nodeId

View File

@ -486,7 +486,12 @@ export default function WorkflowEditorPage() {
</div> </div>
{/* Type-spezifische Konfiguration */} {/* Type-spezifische Konfiguration */}
{selectedNode.type === 'analysis' && ( {selectedNode.type === 'analysis' && (() => {
// Helper: Bestimme aktuellen Mode basierend auf node.data
const isInlineMode = selectedNode.data.inline_template !== null && selectedNode.data.inline_template !== undefined
const isReferenceMode = !isInlineMode
return (
<> <>
{/* Prompt Source Selector */} {/* Prompt Source Selector */}
<div className="config-section"> <div className="config-section">
@ -496,11 +501,12 @@ export default function WorkflowEditorPage() {
<input <input
type="radio" type="radio"
name={`promptSource-${selectedNode.id}`} name={`promptSource-${selectedNode.id}`}
checked={!!selectedNode.data.prompt_slug} checked={isReferenceMode}
onChange={() => { onChange={() => {
// Wechsel zu Reference Mode
handleNodeUpdate(selectedNode.id, { handleNodeUpdate(selectedNode.id, {
prompt_slug: '', inline_template: null, // Inline löschen
inline_template: null prompt_slug: selectedNode.data.prompt_slug || '' // Behalte existierenden slug
}) })
}} }}
style={{ marginRight: '8px' }} style={{ marginRight: '8px' }}
@ -511,11 +517,12 @@ export default function WorkflowEditorPage() {
<input <input
type="radio" type="radio"
name={`promptSource-${selectedNode.id}`} name={`promptSource-${selectedNode.id}`}
checked={!!selectedNode.data.inline_template || (!selectedNode.data.prompt_slug && !selectedNode.data.inline_template)} checked={isInlineMode}
onChange={() => { onChange={() => {
// Wechsel zu Inline Mode
handleNodeUpdate(selectedNode.id, { handleNodeUpdate(selectedNode.id, {
prompt_slug: null, prompt_slug: null, // Reference löschen
inline_template: '' inline_template: selectedNode.data.inline_template || '' // Behalte existierendes template
}) })
}} }}
style={{ marginRight: '8px' }} style={{ marginRight: '8px' }}
@ -525,19 +532,17 @@ export default function WorkflowEditorPage() {
</div> </div>
</div> </div>
{/* Conditional Rendering: Reference oder Inline */} {/* Conditional Rendering: Reference Mode */}
{selectedNode.data.prompt_slug !== null && !selectedNode.data.inline_template && ( {isReferenceMode && (
<div className="config-section"> <div className="config-section">
<label>Basis-Prompt auswählen</label> <label>Basis-Prompt auswählen</label>
<select <select
value={selectedNode.data.prompt_slug ? String(selectedNode.data.prompt_slug) : ''} value={selectedNode.data.prompt_slug || ''}
onChange={(e) => { onChange={(e) => {
const promptSlug = e.target.value const promptSlug = e.target.value
console.log('🎯 Prompt selected:', promptSlug, 'Type:', typeof promptSlug)
const selectedPrompt = availablePrompts.find(p => p.slug === promptSlug) const selectedPrompt = availablePrompts.find(p => p.slug === promptSlug)
console.log('📋 Selected prompt object:', selectedPrompt)
handleNodeUpdate(selectedNode.id, { handleNodeUpdate(selectedNode.id, {
prompt_slug: promptSlug || null, prompt_slug: promptSlug || '',
prompt_name: selectedPrompt?.name || null, prompt_name: selectedPrompt?.name || null,
inline_template: null inline_template: null
}) })
@ -566,8 +571,8 @@ export default function WorkflowEditorPage() {
</div> </div>
)} )}
{/* Inline Template Editor */} {/* Conditional Rendering: Inline Mode */}
{(selectedNode.data.inline_template !== null || !selectedNode.data.prompt_slug) && ( {isInlineMode && (
<InlineTemplateEditor <InlineTemplateEditor
value={selectedNode.data.inline_template || ''} value={selectedNode.data.inline_template || ''}
onChange={(template) => handleNodeUpdate(selectedNode.id, { onChange={(template) => handleNodeUpdate(selectedNode.id, {
@ -581,6 +586,9 @@ export default function WorkflowEditorPage() {
textareaRef={inlineTemplateTextareaRef} textareaRef={inlineTemplateTextareaRef}
/> />
)} )}
</>
)
})()}
<QuestionAugmentationPanel node={selectedNode} onChange={handleNodeUpdate} /> <QuestionAugmentationPanel node={selectedNode} onChange={handleNodeUpdate} />
<FallbackConfig node={selectedNode} edges={edges} nodes={nodes} onChange={handleNodeUpdate} /> <FallbackConfig node={selectedNode} edges={edges} nodes={nodes} onChange={handleNodeUpdate} />
@ -661,6 +669,7 @@ export default function WorkflowEditorPage() {
{showPlaceholderPicker && ( {showPlaceholderPicker && (
<PlaceholderPicker <PlaceholderPicker
nodes={nodes} nodes={nodes}
currentNodeId={selectedNode?.id}
onSelect={handlePlaceholderSelect} onSelect={handlePlaceholderSelect}
onClose={() => setShowPlaceholderPicker(false)} onClose={() => setShowPlaceholderPicker(false)}
/> />

View File

@ -27,7 +27,7 @@
/* ── Sidebar (Node Palette) ─────────────────────────────────────────────── */ /* ── Sidebar (Node Palette) ─────────────────────────────────────────────── */
.workflow-sidebar { .workflow-sidebar {
width: 250px; width: 220px;
background: var(--surface); background: var(--surface);
border-right: 1px solid var(--border); border-right: 1px solid var(--border);
padding: 16px; padding: 16px;
@ -268,7 +268,7 @@
/* ── Config Panel ────────────────────────────────────────────────────────── */ /* ── Config Panel ────────────────────────────────────────────────────────── */
.workflow-config-panel { .workflow-config-panel {
width: 400px; width: 520px;
background: var(--surface); background: var(--surface);
border-left: 1px solid var(--border); border-left: 1px solid var(--border);
padding: 16px; padding: 16px;
@ -453,7 +453,7 @@
} }
.workflow-config-panel { .workflow-config-panel {
width: 350px; width: 450px;
} }
} }