Backend (Mini-Backend 1-2h): - Migration 016: ai_prompts.graph_data JSONB column - workflow_executor: graph_data parameter support (backward-compatible) - prompt_executor: execute_workflow_prompt uses graph_data Frontend (Main effort 25-35h): - WorkflowCanvas: React Flow wrapper component - 5 Custom Nodes: Start, End, Analysis, Logic, Join - 4 Config Panels: QuestionAugmentation, LogicExpression, Fallback, Join - workflowValidation: Structural + logical validation - workflowSerializer: Canvas ↔ JSONB conversion - WorkflowEditorPage: Main orchestration (420 LOC) - Route: /workflow-editor/:id - CSS: workflowEditor.css (300 LOC) Architecture: - Option B: ai_prompts.type='workflow' (not separate table) - panels/ subdirectory for clean separation - WorkflowCanvas reusable component - User GUI identical (Workflows = Prompts) - Backward-compatible (type='pipeline' unchanged) Version: v0.9m → v0.9n (Phase 5 complete) Module: workflow 0.5.0 → 0.6.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
68 lines
2.3 KiB
JavaScript
68 lines
2.3 KiB
JavaScript
/**
|
|
* JoinConfig - Konfiguration für Join Nodes
|
|
*
|
|
* Props:
|
|
* - node: React Flow Node object (type='join')
|
|
* - onChange: (nodeId, updates) => void
|
|
*/
|
|
export function JoinConfig({ node, onChange }) {
|
|
const joinStrategy = node.data.join_strategy || 'wait_all'
|
|
const skipHandling = node.data.skip_handling || 'ignore_skipped'
|
|
const minPaths = node.data.min_paths || 2
|
|
|
|
const handleStrategyChange = (e) => {
|
|
onChange(node.id, { join_strategy: e.target.value })
|
|
}
|
|
|
|
const handleSkipChange = (e) => {
|
|
onChange(node.id, { skip_handling: e.target.value })
|
|
}
|
|
|
|
const handleMinPathsChange = (e) => {
|
|
const value = parseInt(e.target.value) || 2
|
|
onChange(node.id, { min_paths: value })
|
|
}
|
|
|
|
return (
|
|
<div className="config-section">
|
|
<h3>Join-Konfiguration</h3>
|
|
|
|
<label>Join-Strategie</label>
|
|
<select value={joinStrategy} onChange={handleStrategyChange}>
|
|
<option value="wait_all">Alle Pfade warten (wait_all)</option>
|
|
<option value="wait_any">Mindestens ein Pfad (wait_any)</option>
|
|
<option value="best_effort">Verfügbare nutzen (best_effort)</option>
|
|
</select>
|
|
|
|
<div className="help-text">
|
|
{joinStrategy === 'wait_all' && 'Wartet auf alle eingehenden Pfade. Fehler wenn einer fehlt.'}
|
|
{joinStrategy === 'wait_any' && 'Wartet auf mindestens einen Pfad. Erste verfügbare Ausführung.'}
|
|
{joinStrategy === 'best_effort' && 'Fehlertoleranz: Nutzt verfügbare Pfade, auch wenn nicht alle da sind.'}
|
|
</div>
|
|
|
|
<label style={{ marginTop: '16px' }}>Skip-Handling</label>
|
|
<select value={skipHandling} onChange={handleSkipChange}>
|
|
<option value="ignore_skipped">Übersprungene ignorieren</option>
|
|
<option value="use_placeholder">Platzhalter verwenden</option>
|
|
<option value="require_minimum">Mindestanzahl erforderlich</option>
|
|
</select>
|
|
|
|
{skipHandling === 'require_minimum' && (
|
|
<>
|
|
<label style={{ marginTop: '12px' }}>Mindestanzahl Pfade</label>
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
value={minPaths}
|
|
onChange={handleMinPathsChange}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
<div className="help-text" style={{ marginTop: '8px', fontSize: '11px', color: 'var(--text3)' }}>
|
|
💡 Phase 4: Path Consolidation
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|