# Graphischer Workflow-Editor Konzept ## Übersicht Dieses Dokument beschreibt das Konzept für einen graphischen Workflow-Editor, der es ermöglicht, Workflow-Templates visuell zu modellieren. Der Editor wird für die Erstellung und Bearbeitung von Automation-Templates verwendet, die in `formAutomations.js` verwaltet werden. ## Wichtige Terminologie: Workflow vs. WorkflowTemplate **WICHTIG**: Es gibt eine klare Trennung zwischen ausführbaren Workflows und Templates: ### ChatWorkflow (ausführbares Objekt) - **Klasse**: `ChatWorkflow` (in `datamodelChat.py`) - **Zweck**: Ausführbare Workflow-Instanz - **Eigenschaften**: - Hat `id`, `status` (running/completed/stopped/error) - Enthält `tasks` (Liste von TaskItem-Objekten) - Enthält `messages`, `logs`, `stats` - Hat `workflowMode` (automatic oder dynamic) - **Ausführung**: - Kann über Route mit `start` oder `stop` ausgeführt werden - Wird automatisch (scheduled) oder dynamisch (on-demand) gestartet - **Lebenszyklus**: Wird zur Laufzeit erstellt und ausgeführt ### AutomationDefinition (Template-Objekt) - **Klasse**: `AutomationDefinition` (in `datamodelChat.py`) - **Zweck**: Vorlage für ChatWorkflows - **Eigenschaften**: - Hat `id`, `label`, `schedule` (Cron-Pattern) - Enthält `template` (JSON-String mit Workflow-Struktur) - Enthält `placeholders` (Dict mit Platzhalter-Werten) - Hat `active` (ob Automation aktiviert ist) - **Verwendung**: - Wird im Workflow-Editor bearbeitet - Wird verwendet, um ChatWorkflows zu erstellen - Template wird mit Placeholders gefüllt → ChatWorkflow wird erstellt - **Lebenszyklus**: Wird einmal erstellt und mehrfach verwendet ### WorkflowTemplateModel (Editor-Modell) - **Klasse**: `WorkflowTemplateModel` (nur für Editor, nicht in Datenbank) - **Zweck**: Strukturiertes Modell für graphischen Editor - **Verwendung**: - Wird aus `AutomationDefinition.template` (JSON-String) geparst - Wird im Editor bearbeitet - Wird zurück zu `AutomationDefinition.template` konvertiert - **Lebenszyklus**: Nur während Editor-Session vorhanden ### Beziehung zwischen Objekten ``` AutomationDefinition (Template) ↓ (wird verwendet, um zu erstellen) ChatWorkflow (ausführbare Instanz) ↓ (wird ausgeführt) TaskItem → ActionItem → Ergebnisse ``` **Beispiel**: 1. `AutomationDefinition` mit `template: '{"tasks": [...]}'` und `placeholders: {"connectionName": "MyConn"}` 2. Template wird mit Placeholders gefüllt → `ChatWorkflow` wird erstellt 3. `ChatWorkflow` wird gestartet → Tasks werden ausgeführt → Actions werden ausgeführt ## Zielsetzung 1. **Visuelle Modellierung**: Workflows mit Tasks und Actions graphisch erstellen 2. **Parameter-Konfiguration**: Action-Parameter direkt im Editor setzen 3. **Dependency-Management**: Document-Dependencies zwischen Actions visuell darstellen 4. **Template-Generierung**: Automatische Generierung von JSON-Templates aus graphischem Modell ## Datenstruktur-Analyse ### Bestehende Datenmodelle #### ChatWorkflow (datamodelChat.py) - AUSFÜHRBARES OBJEKT ```python class ChatWorkflow(BaseModel): """Ausführbare Workflow-Instanz""" id: str mandateId: str status: str # "running", "completed", "stopped", "error" workflowMode: WorkflowModeEnum # "automatic" oder "dynamic" tasks: list # Liste von TaskItem-Objekten (zur Laufzeit erstellt) messages: List[ChatMessage] logs: List[ChatLog] stats: List[ChatStat] # ... weitere Felder ``` **WICHTIG**: `ChatWorkflow` ist das **ausführbare Objekt**, das über Route mit `start`/`stop` gesteuert werden kann. #### TaskItem (datamodelChat.py) - Teil von ChatWorkflow ```python class TaskItem(BaseModel): """Task innerhalb eines ausführbaren ChatWorkflows""" id: str workflowId: str # Foreign Key zu ChatWorkflow userInput: str status: TaskStatus actionList: List[ActionItem] # Liste von Actions dependencies: List[str] # Task IDs, von denen dieser Task abhängt # ... weitere Felder ``` **WICHTIG**: `TaskItem` existiert nur in ausführbaren `ChatWorkflow`-Instanzen, nicht in Templates. #### ActionItem (datamodelChat.py) - Teil von TaskItem ```python class ActionItem(BaseModel): """Action innerhalb eines TaskItems""" id: str execMethod: str # z.B. "outlook", "sharepoint", "ai" execAction: str # z.B. "readEmails", "uploadDocument" execParameters: Dict[str, Any] # Action-Parameter (mit konkreten Werten) execResultLabel: Optional[str] # Label für resultierende Documents # ... weitere Felder ``` **WICHTIG**: `ActionItem` existiert nur in ausführbaren `ChatWorkflow`-Instanzen, nicht in Templates. #### AutomationDefinition (datamodelChat.py) - TEMPLATE-OBJEKT ```python class AutomationDefinition(BaseModel): """Template/Vorlage für ChatWorkflows""" id: str mandateId: str label: str # User-friendly name template: str # JSON-String mit Workflow-Struktur (enthält Tasks und Actions als Template) placeholders: Dict[str, str] # Platzhalter-Werte (z.B. {"connectionName": "MyConn"}) schedule: str # Cron-Schedule für automatische Ausführung active: bool # Ob Automation aktiviert ist eventId: Optional[str] # Event ID für Event-Management executionLogs: List[Dict[str, Any]] # Logs von Workflow-Ausführungen # ... weitere Felder ``` **WICHTIG**: - `AutomationDefinition` ist das **Template-Objekt**, das im Workflow-Editor bearbeitet wird - `template` Feld enthält JSON-String mit Workflow-Struktur (Tasks und Actions als Vorlage) - `placeholders` enthält Werte für Platzhalter im Template - Wird verwendet, um `ChatWorkflow`-Instanzen zu erstellen ### Document Dependencies Actions können `documentList` Parameter haben, die auf Ergebnisse von anderen Actions verweisen: **Formate**: - `docList:label` - Referenz zu einem Document-Label aus einer vorherigen Action - `docList:messageId:label` - Cross-Round Referenz mit Message-ID **Beispiel**: ```json { "execMethod": "sharepoint", "execAction": "readDocuments", "execParameters": { "connectionReference": "{{connectionName}}", "documentList": ["docList:emails_found"] // Verweist auf execResultLabel "emails_found" } } ``` ## Workflow-Editor UI-Konzept ### Layout-Struktur ``` ┌─────────────────────────────────────────────────────────────┐ │ Workflow Editor - [Template Name] │ ├─────────────────────────────────────────────────────────────┤ │ [Toolbar] [Save] [Validate] [Preview] [Help] │ ├─────────────────────────────────────────────────────────────┤ │ ┌──────────────────┐ ┌─────────────────────────────────┐ │ │ │ Toolbox │ │ Canvas (Graph Editor) │ │ │ │ │ │ │ │ │ │ 📋 Tasks │ │ ┌─────────┐ │ │ │ │ ┌─────────────┐ │ │ │ Task 1 │ │ │ │ │ │ + Task │ │ │ └────┬───┘ │ │ │ │ └─────────────┘ │ │ │ │ │ │ │ │ │ ┌────▼───┐ │ │ │ │ ⚙️ Actions │ │ │Action 1 │ │ │ │ │ ┌─────────────┐ │ │ └────┬───┘ │ │ │ │ │ outlook │ │ │ │ │ │ │ │ │ sharepoint │ │ │ ┌────▼───┐ │ │ │ │ │ ai │ │ │ │Action 2│ │ │ │ │ │ ... │ │ │ └────────┘ │ │ │ │ └─────────────┘ │ │ │ │ │ │ │ │ ┌─────────┐ │ │ │ │ 🔗 Connections │ │ │ Task 2 │ │ │ │ │ (Drag to link) │ │ └─────────┘ │ │ │ └──────────────────┘ └─────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ [Properties Panel] - Selected: Task 1 │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ Task Properties: │ │ │ │ ID: task_1 │ │ │ │ User Input: {{userPrompt}} │ │ │ │ Dependencies: [task_0] │ │ │ │ │ │ │ │ Actions: │ │ │ │ [Action 1] [Action 2] [+ Add Action] │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### Komponenten #### 1. Toolbox (Links) - **Tasks**: Drag & Drop für neue Tasks - **Actions**: Gruppiert nach Methoden (outlook, sharepoint, ai, etc.) - **Connections**: Visueller Hinweis für Dependency-Verbindungen #### 2. Canvas (Mitte) - **Graph-Editor**: Visualisierung des Workflows - **Nodes**: Tasks (große Boxen) und Actions (kleinere Boxen innerhalb von Tasks) - **Edges**: Verbindungen zwischen Tasks (Dependencies) und Actions (Document-Flows) #### 3. Properties Panel (Unten) - **Task Properties**: Wenn Task selektiert - **Action Properties**: Wenn Action selektiert - **Parameter Editor**: Dynamisches Formular basierend auf `WorkflowActionParameter` Definitionen ## Graphische Darstellung ### Task-Node ``` ┌─────────────────────────────────────┐ │ 📋 Task 1 │ │ ───────────────────────────────── │ │ User Input: {{userPrompt}} │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ Action 1.1 │ │ Action 1.2 │ │ │ │ outlook. │ │ sharepoint. │ │ │ │ readEmails │ │ uploadDoc │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ Dependencies: [Task 0] │ └─────────────────────────────────────┘ ``` ### Action-Node ``` ┌─────────────────────────┐ │ ⚙️ outlook.readEmails │ │ ─────────────────────── │ │ connectionRef: {{...}} │ │ query: "unread" │ │ limit: 50 │ │ │ │ Result Label: emails │ │ ─────────────────────── │ │ [Edit] [Delete] │ └─────────────────────────┘ ``` ### Dependency-Verbindungen **Task Dependencies** (gestrichelte Linie): ``` Task 0 ──┐ │ (depends on) ▼ Task 1 ``` **Document Flow** (durchgezogene Linie mit Label): ``` Action 1.1 (emails) ──[docList:emails]──► Action 2.1 ``` ## Datenmodell für Editor ### WorkflowTemplateModel (neues Datenmodell) ```python class WorkflowTemplateModel(BaseModel): """Template-Modell für graphischen Editor""" # Metadaten name: str = Field(description="Template name") description: Optional[str] = Field(None, description="Template description") version: str = Field(default="1.0", description="Template version") # Graph-Struktur tasks: List[TaskTemplateModel] = Field( default_factory=list, description="List of task templates" ) # Placeholders (werden aus Template extrahiert) placeholders: Dict[str, str] = Field( default_factory=dict, description="Placeholder definitions with default values" ) # UI-Metadaten (für Editor) uiMetadata: Optional[Dict[str, Any]] = Field( None, description="UI metadata (node positions, zoom level, etc.)" ) class TaskTemplateModel(BaseModel): """Task-Template für Editor""" id: str = Field(description="Task ID (unique within template)") label: str = Field(description="Task label/name") userInput: str = Field( description="User input template (can contain placeholders like {{userPrompt}})" ) dependencies: List[str] = Field( default_factory=list, description="List of task IDs this task depends on" ) actions: List[ActionTemplateModel] = Field( default_factory=list, description="List of actions in this task" ) # UI-Metadaten position: Optional[Dict[str, float]] = Field( None, description="Node position in editor: {x: number, y: number}" ) class ActionTemplateModel(BaseModel): """Action-Template für Editor""" id: str = Field(description="Action ID (unique within task)") method: str = Field(description="Method name (e.g., 'outlook', 'sharepoint')") action: str = Field(description="Action name (e.g., 'readEmails', 'uploadDocument')") parameters: Dict[str, Any] = Field( default_factory=dict, description="Action parameters (can contain placeholders and document references)" ) resultLabel: Optional[str] = Field( None, description="Result label for document output (used in documentList references)" ) # Document Dependencies (automatisch aus parameters extrahiert) documentDependencies: List[str] = Field( default_factory=list, description="List of result labels this action depends on (from documentList parameter)" ) # UI-Metadaten position: Optional[Dict[str, float]] = Field( None, description="Node position within task: {x: number, y: number}" ) ``` ## Konvertierung: Template ↔ Editor-Modell ### AutomationDefinition.template → Editor-Modell ```python def parseTemplateToEditorModel(templateJson: str) -> WorkflowTemplateModel: """ Parse AutomationDefinition.template (JSON string) to WorkflowTemplateModel INPUT: AutomationDefinition.template (JSON-String) OUTPUT: WorkflowTemplateModel (Editor-Modell) Template-Format (aktuell in AutomationDefinition.template): { "tasks": [ { "id": "task_1", "userInput": "{{userPrompt}}", "dependencies": [], "actions": [ { "method": "outlook", "action": "readEmails", "parameters": { "connectionReference": "{{connectionName}}", "query": "unread", "limit": 50 }, "resultLabel": "emails" } ] } ] } """ templateData = json.loads(templateJson) tasks = [] for taskData in templateData.get("tasks", []): actions = [] for actionData in taskData.get("actions", []): # Extract document dependencies from documentList parameter documentDeps = [] if "documentList" in actionData.get("parameters", {}): docList = actionData["parameters"]["documentList"] if isinstance(docList, list): for ref in docList: if isinstance(ref, str) and ref.startswith("docList:"): # Extract label: docList:label or docList:messageId:label parts = ref.split(":") if len(parts) >= 2: label = parts[-1] # Last part is always the label documentDeps.append(label) actions.append(ActionTemplateModel( id=actionData.get("id", f"action_{len(actions)}"), method=actionData["method"], action=actionData["action"], parameters=actionData.get("parameters", {}), resultLabel=actionData.get("resultLabel"), documentDependencies=documentDeps )) tasks.append(TaskTemplateModel( id=taskData["id"], label=taskData.get("label", taskData["id"]), userInput=taskData.get("userInput", ""), dependencies=taskData.get("dependencies", []), actions=actions )) # Extract placeholders from template placeholders = extractPlaceholders(templateJson) return WorkflowTemplateModel( name="Template", tasks=tasks, placeholders=placeholders ) ``` ### Editor-Modell → AutomationDefinition.template ```python def convertEditorModelToTemplate( editorModel: WorkflowTemplateModel, placeholders: Dict[str, str] ) -> str: """ Convert WorkflowTemplateModel to AutomationDefinition.template JSON string INPUT: WorkflowTemplateModel (Editor-Modell) + placeholders Dict OUTPUT: JSON-String für AutomationDefinition.template Feld """ templateData = { "tasks": [] } for task in editorModel.tasks: taskData = { "id": task.id, "userInput": task.userInput, "dependencies": task.dependencies, "actions": [] } for action in task.actions: # Build documentList from documentDependencies parameters = action.parameters.copy() if action.documentDependencies: documentList = [f"docList:{label}" for label in action.documentDependencies] parameters["documentList"] = documentList actionData = { "method": action.method, "action": action.action, "parameters": parameters } if action.resultLabel: actionData["resultLabel"] = action.resultLabel taskData["actions"].append(actionData) templateData["tasks"].append(taskData) return json.dumps(templateData, indent=2) ``` ## UI-Implementierung ### Technologie-Stack **Empfehlung**: - **Graph-Library**: [Cytoscape.js](https://js.cytoscape.org/) oder [React Flow](https://reactflow.dev/) oder [JointJS](https://www.jointjs.com/) - **Framework**: Vanilla JavaScript (konsistent mit bestehendem Frontend) oder React (falls Migration geplant) - **Styling**: CSS mit bestehenden Styles aus `formGeneric.js` ### HTML-Struktur ```html
Select a node to edit properties