`;
+ html += ``;
+
+ switch (paramDef.frontendType) {
+ case 'text':
+ case 'textarea':
+ html += ``;
+ break;
+ case 'number':
+ html += ``;
+ break;
+ case 'select':
+ case 'userConnection':
+ case 'documentReference':
+ html += ``;
+ break;
+ case 'checkbox':
+ html += ``;
+ break;
+ default:
+ html += ``;
+ }
+
+ html += `${paramDef.description || ''}`;
+ html += `
`;
+
+ return html;
+ }
+
+ saveActionProperties(actionId) {
+ const task = this.model.tasks.find(t =>
+ t.actions.some(a => a.id === actionId)
+ );
+ const action = task?.actions.find(a => a.id === actionId);
+
+ if (!action) return;
+
+ // Collect parameter values from form
+ const parameters = {};
+ // ... collect from form fields ...
+
+ // Update action
+ action.parameters = parameters;
+ action.resultLabel = document.getElementById('action-result-label').value;
+
+ // Update document dependencies
+ action.documentDependencies = this.extractDocumentDependencies(parameters);
+
+ // Re-render graph to show updated connections
+ this.renderGraph();
+ }
+
+ extractDocumentDependencies(parameters) {
+ const deps = [];
+ if (parameters.documentList && Array.isArray(parameters.documentList)) {
+ parameters.documentList.forEach(ref => {
+ if (typeof ref === 'string' && ref.startsWith('docList:')) {
+ const parts = ref.split(':');
+ if (parts.length >= 2) {
+ deps.push(parts[parts.length - 1]); // Last part is label
+ }
+ }
+ });
+ }
+ return deps;
+ }
+
+ saveTemplate() {
+ // Convert editor model to template JSON
+ const templateJson = convertEditorModelToTemplate(
+ this.model,
+ this.getPlaceholders()
+ );
+
+ // Save via API to AutomationDefinition (Template-Objekt)
+ // WICHTIG: Dies speichert das Template, nicht das ausführbare ChatWorkflow
+ return this.saveToAutomationDefinition(templateJson);
+ }
+
+ validateTemplate() {
+ const errors = [];
+
+ // Check: All tasks have at least one action
+ this.model.tasks.forEach(task => {
+ if (task.actions.length === 0) {
+ errors.push(`Task "${task.id}" has no actions`);
+ }
+ });
+
+ // Check: All document dependencies resolve to valid result labels
+ this.model.tasks.forEach(task => {
+ task.actions.forEach(action => {
+ action.documentDependencies.forEach(depLabel => {
+ const sourceAction = this.findActionByResultLabel(depLabel);
+ if (!sourceAction) {
+ errors.push(`Action "${action.method}.${action.action}" depends on unknown result label "${depLabel}"`);
+ }
+ });
+ });
+ });
+
+ // Check: Circular dependencies
+ const circularDeps = this.detectCircularDependencies();
+ if (circularDeps.length > 0) {
+ errors.push(`Circular dependencies detected: ${circularDeps.join(', ')}`);
+ }
+
+ // Check: Required parameters are set
+ // ... weitere Validierungen ...
+
+ return {
+ valid: errors.length === 0,
+ errors: errors
+ };
+ }
+
+ findActionByResultLabel(resultLabel) {
+ for (const task of this.model.tasks) {
+ for (const action of task.actions) {
+ if (action.resultLabel === resultLabel) {
+ return { ...action, taskId: task.id };
+ }
+ }
+ }
+ return null;
+ }
+
+ detectCircularDependencies() {
+ // Graph-based cycle detection
+ // ... Implementation ...
+ return [];
+ }
+}
+
+// Initialize editor
+let editor;
+document.addEventListener('DOMContentLoaded', () => {
+ const templateJson = getInitialTemplate(); // From formAutomations.js
+ editor = new WorkflowEditor('workflow-editor', templateJson);
+});
+```
+
+## Integration mit formAutomations.js
+
+### Erweiterte Automation-Form
+
+```javascript
+// In formAutomations.js
+
+// Add "Edit Template" button to automation form
+function initAutomationForm() {
+ // ... existing code ...
+
+ // Add workflow editor button
+ const templateField = form.querySelector('#entity-template');
+ if (templateField) {
+ const editorButton = document.createElement('button');
+ editorButton.type = 'button';
+ editorButton.className = 'btn btn-secondary';
+ editorButton.innerHTML = ' Open Workflow Editor';
+ editorButton.addEventListener('click', () => {
+ openWorkflowEditor(templateField.value);
+ });
+ templateField.parentNode.appendChild(editorButton);
+ }
+}
+
+function openWorkflowEditor(templateJson) {
+ // Open workflow editor in modal or new tab
+ const modal = document.createElement('div');
+ modal.className = 'workflow-editor-modal';
+ modal.innerHTML = `
+
+
+
Workflow Editor
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(modal);
+
+ // Initialize editor
+ const editor = new WorkflowEditor('workflow-editor-container', templateJson);
+
+ // Save handler
+ document.getElementById('workflow-editor-save').addEventListener('click', () => {
+ const updatedTemplate = editor.saveTemplate();
+ // Update template field in form
+ document.querySelector('#entity-template').value = updatedTemplate;
+ modal.remove();
+ });
+
+ // Cancel handler
+ document.getElementById('workflow-editor-cancel').addEventListener('click', () => {
+ modal.remove();
+ });
+}
+```
+
+## API-Endpunkte
+
+### GET /api/workflows/actions
+Liefert alle verfügbaren Actions (bereits im RBAC-Konzept definiert).
+
+### GET /api/workflows/actions/{method}/{action}
+Liefert Action-Schema mit Parameter-Definitionen:
+
+```json
+{
+ "method": "outlook",
+ "action": "readEmails",
+ "actionId": "outlook.readEmails",
+ "description": "Read emails from Outlook mailbox",
+ "parameters": {
+ "connectionReference": {
+ "name": "connectionReference",
+ "type": "str",
+ "frontendType": "userConnection",
+ "frontendOptions": "user.connection",
+ "required": true,
+ "description": "Microsoft connection label"
+ },
+ "query": {
+ "name": "query",
+ "type": "str",
+ "frontendType": "text",
+ "required": false,
+ "description": "Search query for emails"
+ }
+ }
+}
+```
+
+### GET /api/automations/{automationId}
+Liefert AutomationDefinition für Editor.
+
+**Zweck**: Lädt AutomationDefinition (Template) für Bearbeitung im Editor
+**Rückgabe**: `AutomationDefinition` Objekt mit `template` (JSON-String) und `placeholders`
+
+### POST /api/automations/{automationId}
+Speichert AutomationDefinition (Template).
+
+**Zweck**: Speichert bearbeitetes Template zurück in AutomationDefinition
+**Input**: `AutomationDefinition` Objekt mit aktualisiertem `template` (JSON-String) und `placeholders`
+**Hinweis**: Dies ist das Template-Objekt, nicht das ausführbare ChatWorkflow-Objekt
+
+## Validierung
+
+### Template-Validierung
+
+1. **Struktur-Validierung**:
+ - Alle Tasks haben mindestens eine Action
+ - Alle Task-Dependencies verweisen auf existierende Tasks
+ - Keine zirkulären Dependencies
+
+2. **Action-Validierung**:
+ - Alle Actions haben gültige method/action Kombinationen
+ - Alle required Parameters sind gesetzt
+ - Alle documentList Referenzen verweisen auf existierende resultLabels
+
+3. **Placeholder-Validierung**:
+ - Alle Placeholders im Template sind in placeholders-Dict definiert
+ - Placeholder-Format: `{{PLACEHOLDER_NAME}}`
+
+## Beispiel-Workflow
+
+### Visuelle Darstellung
+
+```
+┌─────────────────────────────────────┐
+│ 📋 Task 1: Read Emails │
+│ ───────────────────────────────── │
+│ User Input: {{userPrompt}} │
+│ │
+│ ┌───────────────────────────────┐ │
+│ │ ⚙️ outlook.readEmails │ │
+│ │ connectionRef: {{conn}} │ │
+│ │ query: "unread" │ │
+│ │ Result: emails │ │
+│ └───────────────┬───────────────┘ │
+└──────────────────┼─────────────────┘
+ │ docList:emails
+ ▼
+┌─────────────────────────────────────┐
+│ 📋 Task 2: Process Documents │
+│ ───────────────────────────────── │
+│ Dependencies: [Task 1] │
+│ │
+│ ┌───────────────────────────────┐ │
+│ │ ⚙️ ai.process │ │
+│ │ documentList: [docList:emails]│ │
+│ │ aiPrompt: {{processPrompt}} │ │
+│ │ Result: processed │ │
+│ └───────────────────────────────┘ │
+└─────────────────────────────────────┘
+```
+
+### Generiertes Template JSON
+
+```json
+{
+ "tasks": [
+ {
+ "id": "task_1",
+ "userInput": "{{userPrompt}}",
+ "dependencies": [],
+ "actions": [
+ {
+ "method": "outlook",
+ "action": "readEmails",
+ "parameters": {
+ "connectionReference": "{{conn}}",
+ "query": "unread",
+ "limit": 50
+ },
+ "resultLabel": "emails"
+ }
+ ]
+ },
+ {
+ "id": "task_2",
+ "userInput": "Process emails",
+ "dependencies": ["task_1"],
+ "actions": [
+ {
+ "method": "ai",
+ "action": "process",
+ "parameters": {
+ "documentList": ["docList:emails"],
+ "aiPrompt": "{{processPrompt}}",
+ "resultType": "json"
+ },
+ "resultLabel": "processed"
+ }
+ ]
+ }
+ ]
+}
+```
+
+## Nächste Schritte
+
+1. **Graph-Library auswählen**: Cytoscape.js vs. React Flow vs. JointJS
+2. **Editor-Komponenten implementieren**: Toolbox, Canvas, Properties Panel
+3. **API-Integration**: Action-Schemas laden, Templates speichern
+4. **Validierung implementieren**: Dependency-Checking, Parameter-Validierung
+5. **Placeholder-Management**: UI für Placeholder-Definition
+6. **Testing**: Unit-Tests für Konvertierungs-Logik
+
+## Offene Fragen
+
+1. **Graph-Library**: Welche Library bevorzugt? (Cytoscape.js ist gut für komplexe Graphen, React Flow ist moderner)
+2. **Positionierung**: Sollen Node-Positionen gespeichert werden oder automatisch layouted?
+3. **Placeholder-UI**: Separate UI für Placeholder-Management oder integriert in Editor?
+4. **Versionierung**: Sollen Templates versioniert werden?
+