fix: Phase 5 - Workflow save + node persistence bugs
KRITISCHE FIXES: 1. Backend: Workflow-Type Support - models.py: graph_data Feld hinzugefügt - models.py: slug Optional (auto-generiert) - prompts.py: 'workflow' in erlaubten Typen - prompts.py: graph_data in INSERT/UPDATE - prompts.py: Auto-Slug-Generierung aus Name - FIX: "Field required: slug" Error behoben 2. Frontend: Node-Updates Persistence - selectedNode sync mit nodes array (useEffect) - FIX: Änderungen gingen verloren (stale state) - FIX: Prompt-Auswahl nicht sichtbar nach Edit - FIX: Fallback-Strategy nicht gespeichert - FIX: Node-Name Änderungen nicht übernommen BEHOBEN: - ❌ Save fehlgeschlagen → ✅ Workflows speicherbar - ❌ Node-Name ignoriert → ✅ Live-Update - ❌ Prompt verschwindet → ✅ Bleibt sichtbar - ❌ Fallback nicht saved → ✅ Persistiert Tested: Backend API akzeptiert jetzt type='workflow' Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e3ef18674a
commit
7d22b052dd
|
|
@ -177,12 +177,12 @@ class StageCreate(BaseModel):
|
|||
|
||||
|
||||
class UnifiedPromptCreate(BaseModel):
|
||||
"""Create a new unified prompt (base or pipeline type)"""
|
||||
"""Create a new unified prompt (base, pipeline, or workflow type)"""
|
||||
name: str
|
||||
slug: str
|
||||
slug: Optional[str] = None # Auto-generated from name if not provided
|
||||
display_name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
type: str # 'base' or 'pipeline'
|
||||
type: str # 'base' | 'pipeline' | 'workflow'
|
||||
category: str = 'ganzheitlich'
|
||||
active: bool = True
|
||||
sort_order: int = 0
|
||||
|
|
@ -195,6 +195,9 @@ class UnifiedPromptCreate(BaseModel):
|
|||
# For pipeline prompts (multi-stage workflow)
|
||||
stages: Optional[list[StageCreate]] = None # Required if type='pipeline'
|
||||
|
||||
# For workflow prompts (visual graph editor)
|
||||
graph_data: Optional[dict] = None # Required if type='workflow'
|
||||
|
||||
|
||||
class UnifiedPromptUpdate(BaseModel):
|
||||
"""Update an existing unified prompt"""
|
||||
|
|
@ -209,6 +212,7 @@ class UnifiedPromptUpdate(BaseModel):
|
|||
output_format: Optional[str] = None
|
||||
output_schema: Optional[dict] = None
|
||||
stages: Optional[list[StageCreate]] = None
|
||||
graph_data: Optional[dict] = None # For workflow type
|
||||
|
||||
|
||||
# ── Pipeline Config Models (Issue #28) ─────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -1380,20 +1380,26 @@ async def execute_unified_prompt(
|
|||
@router.post("/unified")
|
||||
def create_unified_prompt(p: UnifiedPromptCreate, session: dict = Depends(require_admin)):
|
||||
"""
|
||||
Create a new unified prompt (base or pipeline type).
|
||||
Create a new unified prompt (base, pipeline, or workflow type).
|
||||
Admin only.
|
||||
"""
|
||||
with get_db() as conn:
|
||||
cur = get_cursor(conn)
|
||||
|
||||
# Auto-generate slug if not provided (for workflows)
|
||||
if not p.slug:
|
||||
import re
|
||||
base_slug = re.sub(r'[^a-z0-9_]+', '_', p.name.lower()).strip('_')
|
||||
p.slug = f"{base_slug}_{uuid.uuid4().hex[:6]}"
|
||||
|
||||
# Check for duplicate slug
|
||||
cur.execute("SELECT id FROM ai_prompts WHERE slug=%s", (p.slug,))
|
||||
if cur.fetchone():
|
||||
raise HTTPException(status_code=400, detail="Slug already exists")
|
||||
|
||||
# Validate type
|
||||
if p.type not in ['base', 'pipeline']:
|
||||
raise HTTPException(status_code=400, detail="Type must be 'base' or 'pipeline'")
|
||||
if p.type not in ['base', 'pipeline', 'workflow']:
|
||||
raise HTTPException(status_code=400, detail="Type must be 'base', 'pipeline', or 'workflow'")
|
||||
|
||||
# Validate base type has template
|
||||
if p.type == 'base' and not p.template:
|
||||
|
|
@ -1403,6 +1409,10 @@ def create_unified_prompt(p: UnifiedPromptCreate, session: dict = Depends(requir
|
|||
if p.type == 'pipeline' and not p.stages:
|
||||
raise HTTPException(status_code=400, detail="Pipeline prompts require stages")
|
||||
|
||||
# Validate workflow type has graph_data
|
||||
if p.type == 'workflow' and not p.graph_data:
|
||||
raise HTTPException(status_code=400, detail="Workflow prompts require graph_data")
|
||||
|
||||
# Convert stages to JSONB
|
||||
stages_json = None
|
||||
if p.stages:
|
||||
|
|
@ -1426,16 +1436,22 @@ def create_unified_prompt(p: UnifiedPromptCreate, session: dict = Depends(requir
|
|||
|
||||
prompt_id = str(uuid.uuid4())
|
||||
|
||||
# Convert graph_data to JSONB
|
||||
graph_data_json = None
|
||||
if p.graph_data:
|
||||
graph_data_json = json.dumps(p.graph_data)
|
||||
|
||||
cur.execute(
|
||||
"""INSERT INTO ai_prompts
|
||||
(id, slug, name, display_name, description, template, category, active, sort_order,
|
||||
type, stages, output_format, output_schema)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
|
||||
type, stages, output_format, output_schema, graph_data)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
|
||||
(
|
||||
prompt_id, p.slug, p.name, p.display_name, p.description,
|
||||
p.template, p.category, p.active, p.sort_order,
|
||||
p.type, stages_json, p.output_format,
|
||||
json.dumps(p.output_schema) if p.output_schema else None
|
||||
json.dumps(p.output_schema) if p.output_schema else None,
|
||||
graph_data_json
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -1470,8 +1486,8 @@ def update_unified_prompt(prompt_id: str, p: UnifiedPromptUpdate, session: dict
|
|||
updates.append('description=%s')
|
||||
values.append(p.description)
|
||||
if p.type is not None:
|
||||
if p.type not in ['base', 'pipeline']:
|
||||
raise HTTPException(status_code=400, detail="Type must be 'base' or 'pipeline'")
|
||||
if p.type not in ['base', 'pipeline', 'workflow']:
|
||||
raise HTTPException(status_code=400, detail="Type must be 'base', 'pipeline', or 'workflow'")
|
||||
updates.append('type=%s')
|
||||
values.append(p.type)
|
||||
if p.category is not None:
|
||||
|
|
@ -1512,6 +1528,9 @@ def update_unified_prompt(prompt_id: str, p: UnifiedPromptUpdate, session: dict
|
|||
])
|
||||
updates.append('stages=%s')
|
||||
values.append(stages_json)
|
||||
if p.graph_data is not None:
|
||||
updates.append('graph_data=%s')
|
||||
values.append(json.dumps(p.graph_data))
|
||||
|
||||
if not updates:
|
||||
return {"ok": True}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,16 @@ export default function WorkflowEditorPage() {
|
|||
setValidationWarnings(warnings)
|
||||
}, [nodes, edges])
|
||||
|
||||
// Keep selectedNode in sync with nodes array (wichtig für Config Panel!)
|
||||
useEffect(() => {
|
||||
if (selectedNode) {
|
||||
const updatedNode = nodes.find(n => n.id === selectedNode.id)
|
||||
if (updatedNode && updatedNode !== selectedNode) {
|
||||
setSelectedNode(updatedNode)
|
||||
}
|
||||
}
|
||||
}, [nodes])
|
||||
|
||||
// ── Handlers ──────────────────────────────────────────────────────────────
|
||||
|
||||
const onConnect = useCallback(
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user