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:
* - 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
* - onClose: () => void
*
* Features:
* - Lädt registrierte Platzhalter vom Backend (~120+)
* - Extrahiert Workflow-spezifische Node-Outputs
* - Filtert Selbst-Referenzierung (Node kann sich nicht selbst referenzieren)
* - Zeigt Node-Namen (nicht nur IDs)
* - Kategorisiert: System + Workflow
* - Suchfunktion über alle Kategorien
*/
export function PlaceholderPicker({ nodes, onSelect, onClose }) {
export function PlaceholderPicker({ nodes, currentNodeId, onSelect, onClose }) {
const [searchQuery, setSearchQuery] = useState('')
const [systemPlaceholders, setSystemPlaceholders] = useState([])
const [loading, setLoading] = useState(true)
@ -61,8 +63,8 @@ export function PlaceholderPicker({ nodes, onSelect, onClose }) {
loadPlaceholders()
}, [])
// Extrahiere Workflow-spezifische Platzhalter
const workflowPlaceholders = extractWorkflowPlaceholders(nodes)
// Extrahiere Workflow-spezifische Platzhalter (ohne aktuellen Node)
const workflowPlaceholders = extractWorkflowPlaceholders(nodes, currentNodeId)
// Kombiniere beide Listen
const allPlaceholders = [
@ -341,14 +343,19 @@ export function PlaceholderPicker({ nodes, onSelect, onClose }) {
/**
* 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 = []
console.log('🔍 Extracting workflow placeholders from nodes:', nodes)
console.log('🚫 Excluding current node:', currentNodeId)
nodes.forEach(node => {
if (node.type === 'end') return // End Node hat keine Outputs
if (node.id === currentNodeId) return // Selbst-Referenzierung verhindern
const nodeId = node.id
const nodeLabel = node.data?.label || nodeId

View File

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

View File

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