updated concept documents

This commit is contained in:
ValueOn AG 2026-02-04 10:13:46 +01:00
parent c8878489b5
commit 92c9e37f1e
10 changed files with 2648 additions and 24 deletions

View file

@ -705,14 +705,14 @@ async def callAiPlanning(
**Characteristics**:
- Returns unified `AiResponse` model
- Uses `parseJsonWithModel()` for structured parsing
- modelName, priceUsd, processingTime tracked separately (not in response)
- modelName, priceCHF, processingTime tracked separately (not in response)
**Calls**:
1. `self._buildPromptWithPlaceholders(prompt, placeholdersDict)``str`
2. `AiCallRequest(prompt=fullPrompt, context="", options=options)` - Create request
3. `self.aiObjects.call(request)``AiCallResponse`
4. `AiResponse(content=response.content, metadata=None)``AiResponse`
- Note: modelName, priceUsd, processingTime tracked separately (e.g., ChatStat)
- Note: modelName, priceCHF, processingTime tracked separately (e.g., ChatStat)
---

View file

@ -694,7 +694,7 @@ async def processContentPartsWithAi(
return AiCallResponse(
content=mergedContent,
modelName="multiple",
priceUsd=sum(r.priceUsd for r in allResults),
priceCHF=sum(r.priceCHF for r in allResults),
processingTime=sum(r.processingTime for r in allResults),
bytesSent=sum(r.bytesSent for r in allResults),
bytesReceived=sum(r.bytesReceived for r in allResults),

View file

@ -901,7 +901,7 @@ AiCallRequest:
AiCallResponse:
- content: str
- modelName: str
- priceUsd: float
- priceCHF: float
- processingTime: float
- bytesSent: int
- bytesReceived: int

View file

@ -70,7 +70,7 @@ G: Idea
│ │ │ ├── _callWithModel(model, prompt, context, temperature, maxTokens, inputBytes)
│ │ │ ├── If fails → try next model in fallback list
│ │ │ └── If all fail → return error
│ │ └── Returns: AiCallResponse with content, modelName, priceUsd, etc.
│ │ └── Returns: AiCallResponse with content, modelName, priceCHF, etc.
@ -106,7 +106,7 @@ The architecture for interfaceAiObjects.call + interfaceAiObjects.callImage chan
5.4. chunking: if chunkItem size bigger than models maxSize: to produce chunks for the chunkItem, then replace the chunkItem in pipelineContentPartChunks[] with the new chunks
5.5. processing: process chunkItem's in pipelineContentPartChunks and write each processed extraction into processedContentPartChunks and remove chunkItem in pipelineContentPartChunks. This to loop until model fails (to increment model fail counter for the model, to increment model index number, then next loop) or no items anymore (to exit LOOP).
6. Returns: AiCallResponse with content, modelName, priceUsd, etc. based on the delivered data in processedContentPartChunks
6. Returns: AiCallResponse with content, modelName, priceCHF, etc. based on the delivered data in processedContentPartChunks

1064
concepts/Billing-Konzept.md Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,161 @@
# Automation Templates in DB und Editor Konzept
## Übersicht
Templates werden von der Python-Datei in die Datenbank verschoben. Placeholders gehören nicht zum Template, sondern nur zur konkreten **Anwendung** (AutomationDefinition). Template-Liste und **CRUD auf Templates** (Anlegen/Ändern/Löschen) gemäss **RBAC** Nutzer verwalten ihre **eigenen** Templates (z.B. MY-Level); SysAdmin macht nur den **Bootstrap** (initialer Seed). Im UI: **Verwaltung der Automation-Objekte** (bereits vorhanden) und ein **Automation Editor** mit Modus „Definition“ oder „Template“.
---
## 1. Datenmodell
### 1.1 Bestehend: AutomationDefinition (weiter nutzbar)
- **Datei:** `gateway/modules/features/automation/datamodelFeatureAutomation.py`
- **Tabelle:** `AutomationDefinition` (Namespace `data.automation`), RBAC bereits: MY-Level (user-owned).
- **Felder (Auszug):**
- `id`, `mandateId`, `featureInstanceId`, `label`, `schedule`, `active`, `eventId`, `status`, `executionLogs`
- **`template`** (string): JSON des Workflow-Plans. Wird aus einem **AutomationTemplate** übernommen, wenn der User „Aus Template erstellen“ wählt danach keine Referenz mehr, die Definition kann unabhängig weiterbearbeitet werden. Kann auch frei (ohne Template) gepflegt werden.
- **`placeholders`** (Dict[str, str]): Nur hier Werte für `{{KEY:name}}` bei der **spezifischen Anwendung**. Templates enthalten keine Placeholders.
**Fazit:** AutomationDefinition bleibt zentrales Modell; **keine** `templateId`-Referenz. Beim Erzeugen aus einem Template wird der Template-Inhalt in die Definition kopiert; danach wird nur noch die Definition im Editor bearbeitet. Eine Definition kann ihrerseits **als neues Template gespeichert** werden Berechtigung dazu gemäss **RBAC** (wer create auf AutomationTemplate hat).
### 1.2 Neu: AutomationTemplate (nur DB, ohne Placeholders)
- **Zweck:** Vordefinierte Workflow-Vorlagen **ohne** Platzhalter. CRUD gemäss **RBAC** Nutzer ihre eigenen (z.B. MY); Bootstrap (initialer Seed) nur durch SysAdmin/System.
- **Vorschlag Felder:**
- `id` (PK, UUID)
- `label` (**MultiLanguage**, z.B. `{"en": "SharePoint Summary", "de": "SharePoint Zusammenfassung"}`): Anzeigename z.B. „SharePoint Themen Zusammenfassung“
- `overview` (**MultiLanguage**, optional): Kurzbeschreibung
- **`template`** (string): JSON der Workflow-Struktur **mit** Platzhaltern im Format `{{KEY:placeholderName}}`. Enthält z.B. `overview`, `tasks` mit `actionList`, `execMethod`, `execAction`, `execParameters` (dort die Keys). Keine konkreten Werte für Placeholders die kommen erst in AutomationDefinition.placeholders.
- `_createdAt`, `_updatedAt`, `_createdBy` (Systemfelder, optional je nach bestehendem Muster)
**Keine** `placeholders`-Spalte in AutomationTemplate. Placeholders existieren nur in AutomationDefinition bei der Nutzung eines Templates.
### 1.3 Beziehung (ohne Referenz)
- Beim **„Aus Template erstellen“**: Inhalt von `AutomationTemplate.template` wird in eine **neue** AutomationDefinition kopiert (`template` + leere oder vorgeschlagene `placeholders`). Das **Label** wird in der **Sprache des aktuellen Users** übernommen (`definition.label = template.label[userLanguage]`). Es wird **keine** Referenz (templateId) angelegt die Definition ist danach eigenständig und kann beliebig geändert werden.
- Eine **bestehende AutomationDefinition** wird ausschließlich im Editor bearbeitet (template + placeholders).
- **„Als Template speichern“**: Aus einer AutomationDefinition kann ein neues AutomationTemplate angelegt werden Inhalt von `template` wird übernommen, **ohne** konkrete Placeholder-Werte (nur `{{KEY:...}}` bleibt). Berechtigung gemäss **RBAC** (wer create auf AutomationTemplate hat).
---
## 2. RBAC und Bootstrap
### 2.1 AutomationTemplate
- **Bootstrap:** Beim ersten Start (oder Migration) werden die bisherigen Vorlagen aus `subAutomationTemplates.py` in die Tabelle **AutomationTemplate** eingespielt **ohne scharfe Parameter** (keine konkreten Placeholder-Werte aus den bisherigen `parameters`-Blöcken). Nur die reinen Template-JSON-Strukturen mit `{{KEY:...}}`.
- **RBAC:** Gemäss RBAC-System Sichtbarkeit und **CREATE/UPDATE/DELETE** auf AutomationTemplate nach RBAC-Regeln (z.B. Nutzer können **ihre eigenen** Templates anlegen/ändern/löschen, MY-Level). SysAdmin nur für **Bootstrap** (initialer Seed der Vorlagen aus Python).
- **UI-Context:** `ui.feature.automation.templates` Sichtbarkeit und Aktionen (lesen, erstellen, bearbeiten, löschen) nach RBAC.
### 2.2 AutomationDefinition (unverändert)
- SysAdmin: kann alle AutomationDefinition-Einträge sehen/bearbeiten (z.B. über bestehende RBAC-Logik mit mandateId/context).
- User: nur eigene AutomationDefinition-Einträge (MY). Scheduling-Verwaltung: SysAdmin kann alle Schedulings bearbeiten, jeder User nur die eigenen Automations.
---
## 3. Backend-Logik
### 3.1 AutomationTemplate CRUD
- **Neue API** unter z.B. `/api/automation-templates` (oder unter bestehendem Router mit Präfix). **Zugriff gemäss RBAC** Nutzer sehen/bearbeiten/löschen nur ihre eigenen Templates (z.B. MY; Filter/Prüfung über `_createdBy` bzw. RBAC-Regeln für `data.automation.AutomationTemplate`).
- `GET /api/automation-templates` Liste (evtl. paginiert), gefiltert nach RBAC
- `GET /api/automation-templates/{id}` Einzelabruf (nur wenn berechtigt)
- `POST /api/automation-templates` Anlegen (wer create hat)
- `PUT /api/automation-templates/{id}` Aktualisieren (wer update auf diesen Eintrag hat)
- `DELETE /api/automation-templates/{id}` Löschen (wer delete auf diesen Eintrag hat)
- **Bootstrap/Migration (nur SysAdmin/System):** Einmalig Daten aus `subAutomationTemplates.py` lesen, pro „template“-Eintrag einen AutomationTemplate-Datensatz anlegen (nur `label`/`overview` + JSON-`template` mit `{{KEY:...}}`, keine `parameters` speichern). Danach CRUD nur noch über RBAC.
### 3.2 Template-API und Sichtbarkeit
- **GET /api/automations/templates** (bzw. neuer Endpoint für AutomationTemplate): Liste aus **DB** (AutomationTemplate). **Sichtbarkeit gemäss RBAC** Nutzer sehen die Templates, auf die sie Zugriff haben (z.B. eigene + ggf. Gruppen); CREATE/UPDATE/DELETE über gleiche API, Berechtigung pro Request per RBAC.
### 3.3 Nutzbare Actions (Katalog, RBAC-gefiltert)
- **Endpoint** z.B. `GET /api/automations/actions`:
- **Datenquelle:** Pro Action die bestehende **WorkflowActionDefinition** nutzen (`actionId`, `description`, `parameters` mit Typ, `frontendType`, etc.) diese Daten sind pro Action bereits verfügbar (Method-Basis / methodDiscovery).
- **RBAC:** Nur Actions zurückgeben, für die der aktuelle User Berechtigung hat (RESOURCE-Context, `actionId` z.B. `_checkActionPermission(actionId)`). So entsteht die „Übersicht aller nutzbaren Actions“ nach RBAC.
- **Format:** Pro Action die Action-Definition ausliefern (actionId, description, parameters-Schema); optional ein **Beispiel-JSON-Snippet** pro Action (minimales action-Objekt: execMethod, execAction, execParameters, execResultLabel) für Copy/Paste ins Template-JSON.
### 3.4 JSON-Validierung und Placeholder-Extraktion
- Beim Speichern einer AutomationDefinition im Editor (oder in einem separaten „Validate“-Schritt):
- **JSON validieren:** `template`-String parsen, Syntax-Check; bei Fehler Meldung anzeigen.
- **Placeholder-Extraktion:** Regex oder Parser über den `template`-String, alle Vorkommen von `{{KEY:placeholderName}}` sammeln und als vorgeschlagene Keys in `placeholders` anzeigen bzw. bestehende `placeholders` anpassen (neue Keys hinzufügen, nicht mehr vorkommende optional entfernen oder behalten).
---
## 4. UI Admin-Seiten (frontend_nyla)
### 4.1 Seite: Verwaltung der Automation-Objekte und Scheduling (existiert)
- **Route/Seite:** Bereits vorhanden (z.B. AutomationsPage, Route `automations`).
- **Funktion:** Liste der AutomationDefinition-Einträge; Erstellen, Bearbeiten, Löschen, Ausführen, Logs.
- **Berechtigung:** SysAdmin kann alle Automations und deren Scheduling bearbeiten; normale User nur die eigenen. Entspricht dem bestehenden MY-Level plus Admin-Logik (z.B. isSysAdmin sieht alle).
### 4.2 Seite: Automation Editor (neu) ein Editor, zwei Modi
- **Zweck:** Im Editor wird **entweder** ein **AutomationTemplate** bearbeitet **oder** eine **AutomationDefinition** erstellt/bearbeitet. Entscheidend ist ein **Modus-Flag** im Editor (nicht templateId).
**Modus-Flag im Editor:**
- **„Definition bearbeiten / neu erstellen“** (Standard für normale User):
- Inhalt kommt aus einer bestehenden AutomationDefinition oder aus „Aus Template erstellen“ (dann wird Template-Inhalt in eine **neue** Definition kopiert).
- Speichern → **AutomationDefinition** wird erstellt bzw. aktualisiert (template + placeholders). Keine Referenz zum Template.
- **„Template bearbeiten“**:
- Inhalt kommt aus einem AutomationTemplate (oder „Neu“ für leeres Template). Sichtbar/bearbeitbar gemäss **RBAC** (Nutzer nur eigene).
- Speichern → **AutomationTemplate** wird erstellt bzw. aktualisiert. Keine placeholders im Template.
- **„Als Template speichern“** (von einer Definition aus): Aus der aktuell geöffneten AutomationDefinition wird ein neues AutomationTemplate angelegt (nur template-JSON mit `{{KEY:...}}`, keine scharfen Werte). Erlaubt für Nutzer, die gemäss **RBAC** create auf AutomationTemplate haben.
**Saubere Trennung:** Derselbe Editor (gleiche UI: JSON + Platzhalter-Key/Value + Actions-Katalog). Nur das Zielobjekt und die Speicher-Logik unterscheiden sich je nach Modus: Save schreibt entweder in AutomationDefinition oder in AutomationTemplate. Keine templateId nötig.
**Editor-Funktionen (unabhängig vom Modus):**
1. **JSON bearbeiten (Text, Copy/Paste, Validierung)**
- Großes Textfeld (oder Code-Editor) mit dem Inhalt von `template` (aus Definition oder Template).
- Nach Bearbeitung: „Validieren“-Button oder beim Speichern: JSON parsen; bei Fehler Meldung anzeigen.
- Nach Validierung: **Platzhalter anpassen** aus dem JSON alle `{{KEY:xyz}}` extrahieren und in der Placeholder-Verwaltung als Keys anzeigen/aktualisieren. (Bei Template-Modus: Placeholder-UI nur zur Anzeige/Vorschau; gespeichert wird nur das Template ohne Werte.)
2. **Platzhalter als dynamische Key/Value-Objekte**
- Liste aller in `template` vorkommenden Placeholder-Keys (aus Extraktion).
- Pro Key: Wert eingeben. **Typ:** Aus der **Action-Definition** (WorkflowActionParameter, frontendType) der Action, in der der Key vorkommt.
- Werte werden nur bei **Definition** gespeichert (`AutomationDefinition.placeholders`). Beim Template-Modus keine Werte persistiert.
3. **Übersicht nutzbarer Actions (RBAC)**
- Bereich „Nutzbare Actions“: Daten von `GET /api/automations/actions` pro Action die **Action-Definition** (actionId, description, parameters-Schema).
- **Copy/Paste:** Pro Action ein JSON-Snippet (action-Objekt) anzeigen, Buttons „Copy JSON“ / „Insert at cursor“ zum Einfügen ins Template-JSON.
**Navigation:** Von der Automations-Verwaltung: „Neu“ (leer oder „Aus Template erstellen“) bzw. „Bearbeiten“ → Editor im **Definitions-Modus**. Wer gemäss RBAC Templates verwalten darf: „Template bearbeiten“ / „Templates verwalten“ → Editor im **Template-Modus** oder Liste AutomationTemplate (nur eigene bzw. nach RBAC sichtbare).
### 4.3 Template-Verwaltung
- **Seite:** „Templates verwalten“. Sichtbar/verfügbar gemäss **RBAC** Nutzer sehen und verwalten **ihre eigenen** Templates (CRUD nach RBAC, z.B. MY).
- **Inhalt:** CRUD für **AutomationTemplate**. Liste mit Bearbeiten/Neu/Löschen; beim Bearbeiten: `label`, `overview`, `template` (JSON mit `{{KEY:...}}`, ohne konkrete Placeholder-Werte). Template-Liste gefiltert nach Berechtigung („Aus Template erstellen“ nutzt dieselbe Liste).
---
## 5. Zusammenfassung
| Thema | Inhalt |
|------|--------|
| **Datenmodell** | AutomationDefinition unverändert, **keine** templateId. Neu: AutomationTemplate (nur DB, ohne placeholders). Definition aus Template = Kopie des Inhalts; Definition kann als Template gespeichert werden. |
| **Templates** | Von Python in DB; Bootstrap: nur Template-JSON mit `{{KEY:...}}`, **keine scharfen Parameter**. |
| **Placeholders** | Nur in AutomationDefinition; nie im Template. |
| **RBAC Templates** | Sichtbarkeit und CREATE/UPDATE/DELETE gemäss RBAC Nutzer ihre **eigenen** Templates (z.B. MY); SysAdmin nur **Bootstrap** (initialer Seed). |
| **RBAC Automations** | Unverändert: SysAdmin alle, User nur eigene (Scheduling + Definition). |
| **Backend** | AutomationTemplate CRUD gemäss **RBAC** (Nutzer eigene); Bootstrap nur SysAdmin/System; GET nutzbare Actions mit **Daten pro Action-Definition** (WorkflowActionDefinition), RBAC-gefiltert; JSON-Validierung + Placeholder-Extraktion. |
| **UI** | Verwaltung Automations + Scheduling (existiert); ein **Automation Editor** mit **Modus-Flag**: Template bearbeiten vs. Definition erstellen/bearbeiten; „Als Template speichern“ aus Definition (gemäss RBAC). |
---
## 6. Erledigte Klarstellungen
- **Keine templateId:** Eine AutomationDefinition wird aus einem Template **erzeugt** (Inhalt kopiert), danach keine Referenz die Definition wird im Editor bearbeitet und kann sich vom Template unterscheiden. Eine Definition kann ihrerseits **als neues Template gespeichert** werden Berechtigung gemäss **RBAC**.
- **Bootstrap:** Templates dürfen **keine scharfen Parameter** haben (nur `{{KEY:...}}`). **Nur SysAdmin/System** führt den initialen Bootstrap (Seed aus Python) aus; danach CRUD auf Templates **gemäss RBAC** (Nutzer ihre eigenen).
- **GET nutzbare Actions:** **Daten pro Action-Definition** nutzen (WorkflowActionDefinition) pro Action verfügbar.
- **Template-Liste und CRUD:** Sichtbarkeit und CREATE/UPDATE/DELETE AutomationTemplate **gemäss RBAC** Nutzer verwalten ihre **eigenen** Templates; SysAdmin nur Bootstrap.
- **Editor sauber mit Modus-Flag:** Ein Flag im Editor steuert, ob ein **Template** oder eine **Definition** bearbeitet wird. Je nachdem schreibt Speichern in AutomationTemplate oder AutomationDefinition. „Als Template speichern“ erzeugt aus einer Definition ein neues Template erlaubt für jeden mit RBAC-Recht create auf AutomationTemplate.
Dieses Dokument kann als Basis für Implementierungs-Tickets (Backend: Modelle, API, Bootstrap; Frontend: Editor mit Modus, Template-Verwaltung) verwendet werden.

View file

@ -0,0 +1,933 @@
# Automation Templates Frontend Implementation Concept
**Basis:** `doc_automation_templates_db_and_editor_concept.md`
---
## ✅ STATUS: IMPLEMENTIERT (2026-02-03)
**Backend ist vollständig implementiert:**
- ✅ API: `GET/POST/PUT/DELETE /api/automation-templates`
- ✅ Model: `AutomationTemplate` mit `TextMultilingual` Feldern
- ✅ Navigation: `/workflows/automation-templates` in mainSystem.py
- ✅ API: `GET /api/automations/actions` - Actions-Katalog
**Frontend implementiert:**
| Komponente | Status |
|------------|--------|
| `automationApi.ts` - Typen + API-Funktionen | ✅ Implementiert |
| `useAutomations.ts` - Hooks (useWorkflowActions) | ✅ Implementiert |
| `AutomationTemplatesPage.tsx` | ✅ Implementiert |
| `AutomationEditor.tsx` | ✅ **NEU** - Full-screen Editor |
| `ActionsPanel.tsx` | ✅ Implementiert |
| `App.tsx` - Route | ✅ Implementiert |
| `pageRegistry.tsx` - Icon | ✅ Implementiert |
**Neue Dateien:**
- `src/components/AutomationEditor/AutomationEditor.tsx`
- `src/components/AutomationEditor/AutomationEditor.module.css`
- `src/components/AutomationEditor/index.ts`
---
## WICHTIGE HINWEISE
### FormGenerator: Automatische Multilingual-Unterstützung
| Komponente | TextMultilingual-Handling |
|------------|---------------------------|
| **FormGeneratorTable** | ✅ **Automatisch** - erkennt TextMultilingual und rendert in User-Sprache |
| **FormGeneratorForm** | ✅ **Automatisch** - mit `type: 'multilingual'` in Attribut-Definition |
**Kein manueller Code nötig!** Einfach Daten mit TextMultilingual-Feldern übergeben.
### Bestehende vs. Neue API-Typen
| Typ | Quelle | Verwendung |
|-----|--------|------------|
| `AutomationTemplate` | `subAutomationTemplates.py` (Legacy) | Alte hardcoded Templates |
| `AutomationTemplateDB` | DB (neu) | Neue datenbankbasierte Templates |
Die bestehende `fetchAutomationTemplates()` Funktion lädt Legacy-Templates. Für die neuen DB-Templates wird `fetchAutomationTemplatesDB()` verwendet.
---
## 1. Übersicht der Komponenten
| Komponente | Zweck | Status |
|------------|-------|--------|
| `automationApi.ts` | API-Funktionen für Templates + Actions | Erweitern |
| `useAutomations.ts` | Hooks für Templates + Actions | Erweitern |
| `AutomationsPage.tsx` | Liste der Definitions; öffnet Editor | Erweitern |
| `AutomationTemplatesPage.tsx` | Liste der Templates (CRUD) | **Neu** |
| `AutomationEditor.tsx` | Editor mit Modus-Flag (Definition/Template) | **Neu** |
| `ActionsPanel.tsx` | Actions-Katalog mit Copy/Paste | **Neu** |
---
## 2. API-Erweiterungen (automationApi.ts)
### 2.1 Neuer Typ: AutomationTemplate (DB-Version, MultiLanguage)
```typescript
// Multilingual text type (matches backend TextMultilingual)
export interface TextMultilingual {
en: string;
ge?: string;
fr?: string;
it?: string;
}
// NEW: AutomationTemplate from DB (not the old in-memory structure)
export interface AutomationTemplateDB {
id: string;
label: TextMultilingual;
overview?: TextMultilingual;
template: string; // JSON string with {{KEY:...}} placeholders
_createdAt?: number;
_createdBy?: string;
_createdByUserName?: string;
}
// Action definition from backend
export interface WorkflowAction {
method: string;
action: string;
actionId: string;
description: string;
category?: string;
parameters: WorkflowActionParameter[];
exampleJson: {
execMethod: string;
execAction: string;
execParameters: Record<string, any>;
execResultLabel: string;
};
}
export interface WorkflowActionParameter {
name: string;
type: string;
frontendType: string;
required: boolean;
default?: any;
description: string;
frontendOptions?: string | string[];
}
```
### 2.2 Neue API-Funktionen
```typescript
// ============================================================================
// AUTOMATION TEMPLATES (DB) API
// ============================================================================
/**
* Fetch all automation templates (RBAC-filtered: own templates)
* Endpoint: GET /api/automation-templates
*/
export async function fetchAutomationTemplatesDB(
request: ApiRequestFunction
): Promise<AutomationTemplateDB[]> {
const data = await request({
url: '/api/automation-templates',
method: 'get'
});
if (data?.items && Array.isArray(data.items)) {
return data.items;
}
return Array.isArray(data) ? data : [];
}
/**
* Fetch single template by ID
* Endpoint: GET /api/automation-templates/{id}
*/
export async function fetchAutomationTemplateById(
request: ApiRequestFunction,
templateId: string
): Promise<AutomationTemplateDB | null> {
try {
return await request({
url: `/api/automation-templates/${templateId}`,
method: 'get'
});
} catch (error) {
console.error('Error fetching template:', error);
return null;
}
}
/**
* Create new automation template
* Endpoint: POST /api/automation-templates
*/
export async function createAutomationTemplateApi(
request: ApiRequestFunction,
templateData: Omit<AutomationTemplateDB, 'id' | '_createdAt' | '_createdBy'>
): Promise<AutomationTemplateDB> {
return await request({
url: '/api/automation-templates',
method: 'post',
data: templateData
});
}
/**
* Update automation template
* Endpoint: PUT /api/automation-templates/{id}
*/
export async function updateAutomationTemplateApi(
request: ApiRequestFunction,
templateId: string,
templateData: Partial<AutomationTemplateDB>
): Promise<AutomationTemplateDB> {
return await request({
url: `/api/automation-templates/${templateId}`,
method: 'put',
data: templateData
});
}
/**
* Delete automation template
* Endpoint: DELETE /api/automation-templates/{id}
*/
export async function deleteAutomationTemplateApi(
request: ApiRequestFunction,
templateId: string
): Promise<void> {
await request({
url: `/api/automation-templates/${templateId}`,
method: 'delete'
});
}
// ============================================================================
// WORKFLOW ACTIONS API
// ============================================================================
/**
* Fetch available workflow actions (RBAC-filtered)
* Endpoint: GET /api/automations/actions
*/
export async function fetchWorkflowActions(
request: ApiRequestFunction
): Promise<WorkflowAction[]> {
const data = await request({
url: '/api/automations/actions',
method: 'get'
});
return data?.actions || [];
}
```
---
## 3. Hooks-Erweiterungen (useAutomations.ts)
### 3.1 Neuer Hook: useAutomationTemplates
```typescript
/**
* Hook for managing AutomationTemplates (DB)
*/
export function useAutomationTemplates() {
const [templates, setTemplates] = useState<AutomationTemplateDB[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { request } = useApiRequest();
const { checkPermission } = usePermissions();
const [permissions, setPermissions] = useState<UserPermissions | null>(null);
const fetchTemplates = useCallback(async () => {
setLoading(true);
try {
const data = await fetchAutomationTemplatesDB(request);
setTemplates(data);
} catch (e: any) {
setError(e.message);
setTemplates([]);
} finally {
setLoading(false);
}
}, [request]);
const fetchPermissions = useCallback(async () => {
const perms = await checkPermission('DATA', 'AutomationTemplate');
setPermissions(perms);
return perms;
}, [checkPermission]);
const createTemplate = useCallback(async (data: Omit<AutomationTemplateDB, 'id'>) => {
return await createAutomationTemplateApi(request, data);
}, [request]);
const updateTemplate = useCallback(async (id: string, data: Partial<AutomationTemplateDB>) => {
return await updateAutomationTemplateApi(request, id, data);
}, [request]);
const deleteTemplate = useCallback(async (id: string) => {
await deleteAutomationTemplateApi(request, id);
}, [request]);
return {
templates,
loading,
error,
permissions,
fetchTemplates,
fetchPermissions,
createTemplate,
updateTemplate,
deleteTemplate,
};
}
```
### 3.2 Neuer Hook: useWorkflowActions
```typescript
/**
* Hook for fetching available workflow actions (RBAC-filtered)
*/
export function useWorkflowActions() {
const [actions, setActions] = useState<WorkflowAction[]>([]);
const [loading, setLoading] = useState(false);
const { request } = useApiRequest();
const fetchActions = useCallback(async () => {
setLoading(true);
try {
const data = await fetchWorkflowActions(request);
setActions(data);
} catch (e) {
console.error('Error fetching actions:', e);
setActions([]);
} finally {
setLoading(false);
}
}, [request]);
return { actions, loading, fetchActions };
}
```
---
## 4. Neue Seite: AutomationTemplatesPage.tsx
### 4.1 Imports
```typescript
import React, { useState, useMemo, useEffect } from 'react';
import { FaSync, FaPlus, FaFileAlt } from 'react-icons/fa';
import { useLanguage } from '../../providers/language/LanguageContext';
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
import { useAutomationTemplates, AutomationTemplateDB, TextMultilingual } from '../../hooks/useAutomations';
import { AutomationEditor } from '../../components/AutomationEditor'; // Neu
import styles from '../admin/Admin.module.css';
```
### 4.2 Struktur
```typescript
/**
* AutomationTemplatesPage
*
* CRUD für AutomationTemplates (eigene Templates verwalten).
* Nutzt FormGeneratorTable wie AutomationsPage.
*/
export const AutomationTemplatesPage: React.FC = () => {
const {
templates,
loading,
permissions,
fetchTemplates,
fetchPermissions,
createTemplate,
updateTemplate,
deleteTemplate,
} = useAutomationTemplates();
const [showEditor, setShowEditor] = useState(false);
const [editingTemplate, setEditingTemplate] = useState<AutomationTemplateDB | null>(null);
useEffect(() => {
fetchTemplates();
fetchPermissions();
}, []);
// Check permissions
const canCreate = permissions?.create !== 'n';
const canUpdate = permissions?.update !== 'n';
const canDelete = permissions?.delete !== 'n';
// Open Editor in "Template mode"
const handleEdit = (template: AutomationTemplateDB) => {
setEditingTemplate(template);
setShowEditor(true);
};
const handleCreate = () => {
setEditingTemplate(null); // New template
setShowEditor(true);
};
const handleSave = async (data: Partial<AutomationTemplateDB>) => {
if (editingTemplate) {
await updateTemplate(editingTemplate.id, data);
} else {
await createTemplate(data as any);
}
setShowEditor(false);
fetchTemplates();
};
// Columns - FormGeneratorTable rendert TextMultilingual automatisch in User-Sprache!
const columns = useMemo(() => [
{ key: 'label', label: 'Label', type: 'string' as const, sortable: true, searchable: true },
{ key: 'overview', label: 'Beschreibung', type: 'string' as const, width: 300 },
{ key: '_createdByUserName', label: 'Erstellt von', type: 'string' as const },
], []);
// Handle delete
const handleDelete = async (template: AutomationTemplateDB) => {
await deleteTemplate(template.id);
fetchTemplates();
};
return (
<div className={styles.adminPage}>
<div className={styles.pageHeader}>
<div>
<h1 className={styles.pageTitle}>Automation-Vorlagen</h1>
<p className={styles.pageSubtitle}>Verwalten Sie Ihre Workflow-Vorlagen</p>
</div>
<div className={styles.headerActions}>
<button className={styles.secondaryButton} onClick={() => fetchTemplates()}>
<FaSync /> Aktualisieren
</button>
{canCreate && (
<button className={styles.primaryButton} onClick={handleCreate}>
<FaPlus /> Neue Vorlage
</button>
)}
</div>
</div>
<div className={styles.tableContainer}>
<FormGeneratorTable
data={templates as any[]}
columns={columns}
loading={loading}
pagination={true}
pageSize={25}
searchable={true}
sortable={true}
actionButtons={[
...(canUpdate ? [{
type: 'edit' as const,
onAction: handleEdit,
title: 'Bearbeiten',
}] : []),
...(canDelete ? [{
type: 'delete' as const,
title: 'Löschen',
}] : []),
]}
onDelete={handleDelete}
emptyMessage="Keine Vorlagen vorhanden"
/>
</div>
{showEditor && (
<AutomationEditor
mode="template"
initialData={editingTemplate}
onSave={handleSave}
onCancel={() => setShowEditor(false)}
/>
)}
</div>
);
};
```
---
## 5. Neue Komponente: AutomationEditor.tsx
### 5.1 Konzept: Saubere Lösung via FormGeneratorForm
**Kernidee:** FormGeneratorForm nutzen für ALLE Felder inkl. Multilingual!
```typescript
/**
* Attribute-Definitionen je nach Modus
* FormGeneratorForm rendert type:'multilingual' automatisch mit Sprach-Tabs
*/
function getEditorAttributes(mode: 'definition' | 'template'): AttributeDefinition[] {
const baseAttributes: AttributeDefinition[] = [
{
name: 'template',
label: 'Template JSON',
type: 'textarea',
required: true,
minRows: 15,
maxRows: 30,
description: 'Workflow-Definition als JSON'
}
];
if (mode === 'template') {
return [
{ name: 'label', label: 'Label', type: 'multilingual', required: true },
{ name: 'overview', label: 'Beschreibung', type: 'multilingual' },
...baseAttributes
];
} else {
return [
{ name: 'label', label: 'Label', type: 'text', required: true },
{ name: 'schedule', label: 'Schedule (Cron)', type: 'text' },
{ name: 'active', label: 'Aktiv', type: 'checkbox' },
...baseAttributes,
{ name: 'placeholders', label: 'Platzhalter', type: 'textarea', description: 'JSON-Objekt' }
];
}
}
```
### 5.2 Komponente
```typescript
interface AutomationEditorProps {
mode: 'definition' | 'template';
initialData?: AutomationTemplateDB | Automation | null;
onSave: (data: any) => Promise<void>;
onCancel: () => void;
}
export const AutomationEditor: React.FC<AutomationEditorProps> = ({
mode,
initialData,
onSave,
onCancel,
}) => {
const [showActionsPanel, setShowActionsPanel] = useState(false);
// Daten für FormGeneratorForm vorbereiten
const formData = useMemo(() => {
if (!initialData) return {};
return {
...initialData,
template: typeof initialData.template === 'string'
? initialData.template
: JSON.stringify(initialData.template, null, 2)
};
}, [initialData]);
return (
<div className={styles.editorOverlay}>
<div className={styles.editorContainer}>
<header>
<h2>{mode === 'template' ? 'Template bearbeiten' : 'Automation bearbeiten'}</h2>
<button onClick={onCancel}><FaTimes /></button>
</header>
<div className={styles.editorContent}>
{/* Main Form - FormGeneratorForm macht alles! */}
<div className={styles.editorMain}>
<FormGeneratorForm
attributes={getEditorAttributes(mode)}
data={formData}
mode={initialData ? 'edit' : 'create'}
onSubmit={onSave}
onCancel={onCancel}
submitButtonText="Speichern"
cancelButtonText="Abbrechen"
/>
</div>
{/* Actions Panel (optional, für Copy/Paste) */}
<div className={styles.actionsPanel}>
<button onClick={() => setShowActionsPanel(!showActionsPanel)}>
{showActionsPanel ? 'Actions ausblenden' : 'Actions anzeigen'}
</button>
{showActionsPanel && <ActionsPanel />}
</div>
</div>
</div>
</div>
);
};
```
**Vorteile dieser Lösung:**
- Kein manueller Code für Multilingual-Inputs
- FormGeneratorForm validiert automatisch
- Konsistent mit dem Rest der Anwendung
- Weniger Code, weniger Bugs
---
## 6. Neue Komponente: ActionsPanel.tsx
### 6.1 Struktur
```typescript
interface ActionsPanelProps {
onInsert: (actionJson: string) => void;
onCopy?: (actionJson: string) => void;
}
export const ActionsPanel: React.FC<ActionsPanelProps> = ({ onInsert, onCopy }) => {
const { actions, loading, fetchActions } = useWorkflowActions();
const [filter, setFilter] = useState('');
const [expandedAction, setExpandedAction] = useState<string | null>(null);
useEffect(() => {
fetchActions();
}, []);
const filteredActions = useMemo(() => {
if (!filter) return actions;
const lower = filter.toLowerCase();
return actions.filter(a =>
a.method.toLowerCase().includes(lower) ||
a.action.toLowerCase().includes(lower) ||
a.description.toLowerCase().includes(lower)
);
}, [actions, filter]);
// Group by method
const groupedActions = useMemo(() => {
const groups: Record<string, WorkflowAction[]> = {};
filteredActions.forEach(action => {
if (!groups[action.method]) {
groups[action.method] = [];
}
groups[action.method].push(action);
});
return groups;
}, [filteredActions]);
const handleCopy = (action: WorkflowAction) => {
const json = JSON.stringify(action.exampleJson, null, 2);
navigator.clipboard.writeText(json);
onCopy?.(json);
};
const handleInsert = (action: WorkflowAction) => {
const json = JSON.stringify(action.exampleJson, null, 2);
onInsert(json);
};
return (
<div className={styles.actionsPanel}>
<h3>Verfügbare Actions</h3>
<input
type="text"
placeholder="Suchen..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
{loading ? (
<div>Lade Actions...</div>
) : (
<div className={styles.actionsList}>
{Object.entries(groupedActions).map(([method, methodActions]) => (
<div key={method} className={styles.methodGroup}>
<h4>{method}</h4>
{methodActions.map(action => (
<div key={action.actionId} className={styles.actionItem}>
<div
className={styles.actionHeader}
onClick={() => setExpandedAction(
expandedAction === action.actionId ? null : action.actionId
)}
>
<span className={styles.actionName}>
{action.action}
</span>
<span className={styles.actionDesc}>
{action.description}
</span>
</div>
{expandedAction === action.actionId && (
<div className={styles.actionDetails}>
<h5>Parameter:</h5>
<ul>
{action.parameters.map(p => (
<li key={p.name}>
<strong>{p.name}</strong>
{p.required && <span className={styles.required}>*</span>}
: {p.description}
</li>
))}
</ul>
<h5>Beispiel JSON:</h5>
<pre>{JSON.stringify(action.exampleJson, null, 2)}</pre>
<div className={styles.actionButtons}>
<button onClick={() => handleCopy(action)}>
<FaCopy /> Kopieren
</button>
<button onClick={() => handleInsert(action)}>
<FaPlus /> Einfügen
</button>
</div>
</div>
)}
</div>
))}
</div>
))}
</div>
)}
</div>
);
};
```
---
## 7. Anpassungen in AutomationsPage.tsx
### 7.1 Editor-Integration
Bestehende `handleEditClick` und Template-Auswahl anpassen:
```typescript
// State for editor
const [showEditor, setShowEditor] = useState(false);
const [editorMode, setEditorMode] = useState<'definition' | 'template'>('definition');
const [editorInitialData, setEditorInitialData] = useState<Automation | AutomationTemplateDB | null>(null);
// Edit existing definition
const handleEditClick = async (automation: Automation) => {
const fullAutomation = await fetchAutomationById(automation.id);
setEditorInitialData(fullAutomation);
setEditorMode('definition');
setShowEditor(true);
};
// Create from template
const handleCreateFromTemplate = async (template: AutomationTemplateDB) => {
// Extract label in user's language
const userLang = getUserLanguage(); // e.g., 'de' or 'en'
const label = template.label[userLang] || template.label.en || 'New Automation';
// Pre-fill new definition
const newDefinition: Partial<Automation> = {
label: label,
template: template.template,
placeholders: {},
schedule: '0 22 * * *',
active: false,
mandateId: mandateId,
featureInstanceId: featureInstanceId,
};
setEditorInitialData(newDefinition as Automation);
setEditorMode('definition');
setShowEditor(true);
};
// Save handler from editor
const handleEditorSave = async (data: any) => {
if (editorMode === 'definition') {
if (editorInitialData?.id) {
await handleAutomationUpdate(editorInitialData.id, data);
} else {
await handleAutomationCreate(data);
}
}
// Template save is handled in AutomationTemplatesPage
setShowEditor(false);
refetch();
};
```
### 7.2 Template-Auswahl Modal anpassen
Bisherige Template-Auswahl nutzt jetzt `AutomationTemplateDB` statt altes Format:
```typescript
const [templatesDB, setTemplatesDB] = useState<AutomationTemplateDB[]>([]);
const loadTemplates = async () => {
setLoadingTemplates(true);
const data = await fetchAutomationTemplatesDB(request);
setTemplatesDB(data);
setLoadingTemplates(false);
};
// In Template-Modal: Display label in current language
// Nutze inline-Zugriff oder FormGeneratorTable für Liste
const { currentLanguage } = useLanguage();
const getLang = (ml: TextMultilingual | undefined) =>
ml?.[currentLanguage as keyof TextMultilingual] || ml?.en || '';
{templatesDB.map(template => (
<div key={template.id} onClick={() => handleCreateFromTemplate(template)}>
<h4>{getLang(template.label)}</h4>
<p>{getLang(template.overview)}</p>
</div>
))}
```
---
## 8. Navigation / Routing
### Architektur-Hinweis: ObjectKey-Formate
| Kontext | Format | Beispiel | Verwendung |
|---------|--------|----------|------------|
| Backend Navigation | `ui.system.xxx` | `ui.system.automations` | NAVIGATION_SECTIONS in mainSystem.py |
| Frontend Icons | `page.system.xxx` | `page.system.automations` | pageRegistry.tsx PAGE_ICONS |
| RBAC DATA | `data.automation.xxx` | `data.automation.AutomationTemplate` | Tabellen-Zugriffsregeln |
| RBAC UI | `ui.feature.automation.xxx` | `ui.feature.automation.templates` | Feature-UI-Objekte |
Die Sidebar-Navigation kommt vom **Backend** (`/api/navigation`), nicht aus pageRegistry. PageRegistry liefert nur die **Icons** zu den vom Backend gelieferten Items.
---
### 8.1 Backend: mainSystem.py (NAVIGATION_SECTIONS)
**WICHTIG:** Die Navigation wird vom Backend gesteuert! In `gateway/modules/system/mainSystem.py` unter `NAVIGATION_SECTIONS` im Abschnitt "workflows" hinzufügen:
```python
{
"id": "automation-templates",
"objectKey": "ui.system.automation-templates",
"label": {"en": "Templates", "de": "Vorlagen", "fr": "Modèles"},
"icon": "FaFileAlt", # Oder FaCopy für Vorlagen
"path": "/workflows/automation-templates",
"order": 35, # Nach automations (30)
"public": True,
},
```
### 8.2 Frontend: App.tsx
Route innerhalb des `<Route path="workflows">` Blocks hinzufügen:
```typescript
// In App.tsx, im workflows-Block:
<Route path="workflows">
<Route path="playground" element={<PlaygroundPage />} />
<Route path="list" element={<WorkflowsPage />} />
<Route path="automations" element={<AutomationsPage />} />
<Route path="automation-templates" element={<AutomationTemplatesPage />} /> {/* NEU */}
</Route>
```
**Import hinzufügen:**
```typescript
import { AutomationTemplatesPage } from './pages/workflows/AutomationTemplatesPage';
```
### 8.3 Frontend: pageRegistry.tsx
**WICHTIG:** Format ist `page.system.xxx`, nicht `ui.system.xxx`!
```typescript
// Import hinzufügen (FaFileAlt ist bereits importiert):
// import { ..., FaFileAlt, ... } from 'react-icons/fa';
// In PAGE_ICONS hinzufügen:
'page.system.automation-templates': <FaFileAlt />,
```
### 8.4 Frontend: pages/workflows/index.ts
Export hinzufügen:
```typescript
export { PlaygroundPage } from './PlaygroundPage';
export { WorkflowsPage } from './WorkflowsPage';
export { AutomationsPage } from './AutomationsPage';
export { AutomationTemplatesPage } from './AutomationTemplatesPage'; // NEU
```
### 8.5 Locales (de.ts, en.ts, fr.ts)
Übersetzungen hinzufügen:
**de.ts:**
```typescript
'nav.automation-templates': 'Vorlagen',
'automation-templates.title': 'Automation-Vorlagen',
'automation-templates.create': 'Neue Vorlage',
'automation-templates.edit': 'Vorlage bearbeiten',
'automation-templates.empty': 'Keine Vorlagen vorhanden',
```
**en.ts:**
```typescript
'nav.automation-templates': 'Templates',
'automation-templates.title': 'Automation Templates',
'automation-templates.create': 'New Template',
'automation-templates.edit': 'Edit Template',
'automation-templates.empty': 'No templates available',
```
---
## 9. Hilfsfunktionen
> **Hinweis:** `getMultilingualText()` ist NICHT nötig - FormGeneratorTable rendert TextMultilingual automatisch in der User-Sprache via `formatTextMultilingual()` intern.
### 9.1 extractPlaceholdersFromJson
```typescript
/**
* Extract {{KEY:name}} placeholders from JSON string
*/
export function extractPlaceholdersFromJson(jsonString: string): string[] {
const regex = /\{\{KEY:(\w+)\}\}/g;
const keys: string[] = [];
let match;
while ((match = regex.exec(jsonString)) !== null) {
if (!keys.includes(match[1])) {
keys.push(match[1]);
}
}
return keys;
}
```
---
## 10. Zusammenfassung der Dateien
| Datei | Änderung |
|-------|----------|
| `api/automationApi.ts` | Neue Typen + API-Funktionen für Templates (DB) und Actions |
| `hooks/useAutomations.ts` | Neue Hooks: `useAutomationTemplates`, `useWorkflowActions` |
| `pages/workflows/AutomationsPage.tsx` | Editor-Integration, Template-Auswahl mit DB-Templates |
| `pages/workflows/AutomationTemplatesPage.tsx` | **Neu**: CRUD für eigene Templates |
| `components/AutomationEditor/AutomationEditor.tsx` | **Neu**: Editor mit Modus-Flag |
| `components/AutomationEditor/ActionsPanel.tsx` | **Neu**: Actions-Katalog |
| `App.tsx` | Neue Route für AutomationTemplatesPage |
| `config/pageRegistry.tsx` | Icon für neue Seite |
---
## 11. Implementierungsreihenfolge
1. **API-Typen und Funktionen** (`automationApi.ts`)
2. **Hooks** (`useAutomations.ts` erweitern)
3. **ActionsPanel** (unabhängig testbar)
4. **AutomationEditor** (Kern-Komponente)
5. **AutomationTemplatesPage** (CRUD für Templates)
6. **AutomationsPage anpassen** (Editor-Integration)
7. **Routing** (App.tsx, Navigation)

View file

@ -0,0 +1,466 @@
# Automation Templates Gateway Implementation Concept
**Basis:** `doc_automation_templates_db_and_editor_concept.md`
---
## ⚠️ STATUS: BEREITS IMPLEMENTIERT
**Das Backend ist vollständig implementiert!** Folgende Komponenten existieren bereits:
| Komponente | Status | Datei |
|------------|--------|-------|
| AutomationTemplate Model | ✅ Vorhanden | `datamodelFeatureAutomation.py:49` |
| RBAC Namespace | ✅ Vorhanden | `interfaceRbac.py:69` |
| API Routes (CRUD) | ✅ Vorhanden | `routeFeatureAutomation.py:391-607` |
| Interface Methods | ✅ Vorhanden | `interfaceFeatureAutomation.py:411-575` |
| Navigation Entry | ✅ Vorhanden | `mainSystem.py:96-103` |
**Was noch fehlt:**
- Bootstrap-Funktion für initiale Template-Migration (optional)
**Actions-Katalog:** ✅ Bereits vorhanden unter `GET /api/automations/actions` (Zeile 293)
---
## 1. Bestehendes Datenmodell: AutomationTemplate
### 1.1 Datei: `gateway/modules/features/automation/datamodelFeatureAutomation.py`
Neues Modell **AutomationTemplate** hinzufügen (neben bestehendem `AutomationDefinition`):
```python
from modules.datamodels.datamodelUtils import TextMultilingual
class AutomationTemplate(BaseModel):
"""Automation-Vorlage ohne scharfe Placeholder-Werte."""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Primary key",
json_schema_extra={"frontend_type": "text", "frontend_readonly": True}
)
label: TextMultilingual = Field(
description="Template name (multilingual)",
json_schema_extra={"frontend_type": "multilingual", "frontend_required": True}
)
overview: Optional[TextMultilingual] = Field(
None,
description="Short description (multilingual)",
json_schema_extra={"frontend_type": "multilingual", "frontend_required": False}
)
template: str = Field(
description="JSON workflow structure with {{KEY:...}} placeholders",
json_schema_extra={"frontend_type": "textarea", "frontend_required": True}
)
# System fields (_createdAt, _createdBy, etc.) werden automatisch vom DB-Connector gesetzt
registerModelLabels(
"AutomationTemplate",
{"en": "Automation Template", "de": "Automation-Vorlage", "fr": "Modèle d'automatisation"},
{
"id": {"en": "ID", "de": "ID", "fr": "ID"},
"label": {"en": "Label", "de": "Bezeichnung", "fr": "Libellé"},
"overview": {"en": "Overview", "de": "Übersicht", "fr": "Aperçu"},
"template": {"en": "Template", "de": "Vorlage", "fr": "Modèle"},
},
)
```
### 1.2 Namespace & Tabelle
- **Namespace:** `data.automation` (wie AutomationDefinition)
- **Tabelle:** `AutomationTemplate`
- Registrierung in `interfaceRbac.py` (TABLE_NAMESPACE_MAP):
```python
"AutomationTemplate": "automation",
```
---
## 2. RBAC für AutomationTemplate
### 2.1 Bootstrap-Regeln (interfaceBootstrap.py)
In `_createTableSpecificRules()` hinzufügen:
```python
# AutomationTemplate: MY-level (user-owned), like AutomationDefinition
for roleId in [adminId, userId]:
if roleId:
tableRules.append(AccessRule(
roleId=roleId,
context=AccessRuleContext.DATA,
item="data.automation.AutomationTemplate",
view=True,
read=AccessLevel.MY,
create=AccessLevel.MY,
update=AccessLevel.MY,
delete=AccessLevel.MY,
))
if viewerId:
tableRules.append(AccessRule(
roleId=viewerId,
context=AccessRuleContext.DATA,
item="data.automation.AutomationTemplate",
view=True,
read=AccessLevel.MY,
create=AccessLevel.NONE,
update=AccessLevel.NONE,
delete=AccessLevel.NONE,
))
```
In `_ensureDataContextRules()` (Zeile ~845) ergänzen:
```python
"data.automation.AutomationTemplate",
```
### 2.2 UI-Regeln
In `mainAutomation.py` bereits vorhanden:
```python
{"objectKey": "ui.feature.automation.templates", ...}
```
Sichtbarkeit gemäss RBAC (alle mit Berechtigung; nicht nur SysAdmin).
### 2.3 Navigation (mainSystem.py)
In `NAVIGATION_SECTIONS` unter dem "workflows" Abschnitt hinzufügen:
```python
{
"id": "automation-templates",
"objectKey": "ui.system.automation-templates",
"label": {"en": "Templates", "de": "Vorlagen", "fr": "Modèles"},
"icon": "FaFileAlt",
"path": "/workflows/automation-templates",
"order": 35, # Nach automations (30)
"public": True,
},
```
**WICHTIG:** Das `objectKey` Format für Navigation ist `ui.system.xxx`, nicht `page.system.xxx`!
---
## 3. API Routes für AutomationTemplate
### 3.1 Neue Route-Datei oder erweitern: `routeFeatureAutomation.py`
Endpoints unter `/api/automation-templates` (oder `/api/automations/templates` erweitern):
```python
from modules.features.automation.datamodelFeatureAutomation import AutomationTemplate
# GET /api/automation-templates - Liste (RBAC-gefiltert)
@router.get("/templates", response_model=PaginatedResponse[AutomationTemplate])
async def get_templates(
request: Request,
pagination: Optional[str] = Query(None),
context: RequestContext = Depends(getRequestContext)
):
"""Get automation templates, filtered by RBAC (MY = own templates)."""
chatInterface = getChatInterface(context.user, ...)
result = chatInterface.getAllAutomationTemplates(pagination=paginationParams)
return JSONResponse(content=result)
# GET /api/automation-templates/{id}
@router.get("/templates/{templateId}", response_model=AutomationTemplate)
async def get_template(templateId: str, context: RequestContext = Depends(getRequestContext)):
chatInterface = getChatInterface(context.user, ...)
template = chatInterface.getAutomationTemplate(templateId)
if not template:
raise HTTPException(404, "Template not found")
return template
# POST /api/automation-templates
@router.post("/templates", response_model=AutomationTemplate)
async def create_template(
request: Request,
templateData: Dict[str, Any] = Body(...),
context: RequestContext = Depends(getRequestContext)
):
chatInterface = getChatInterface(context.user, ...)
return chatInterface.createAutomationTemplate(templateData)
# PUT /api/automation-templates/{id}
@router.put("/templates/{templateId}", response_model=AutomationTemplate)
async def update_template(
templateId: str,
templateData: Dict[str, Any] = Body(...),
context: RequestContext = Depends(getRequestContext)
):
chatInterface = getChatInterface(context.user, ...)
return chatInterface.updateAutomationTemplate(templateId, templateData)
# DELETE /api/automation-templates/{id}
@router.delete("/templates/{templateId}")
async def delete_template(templateId: str, context: RequestContext = Depends(getRequestContext)):
chatInterface = getChatInterface(context.user, ...)
success = chatInterface.deleteAutomationTemplate(templateId)
if not success:
raise HTTPException(404, "Template not found or no permission")
return {"success": True}
```
### 3.2 Interface-Methoden (interfaceDbChat.py)
Analog zu `getAllAutomationDefinitions`, `createAutomationDefinition`, etc.:
```python
def getAllAutomationTemplates(self, pagination=None) -> Union[List[Dict], PaginatedResult]:
"""Returns templates filtered by RBAC (MY = own templates)."""
filteredTemplates = getRecordsetWithRBAC(self.db, AutomationTemplate, self.currentUser)
# ... pagination, enrichment
return filteredTemplates
def getAutomationTemplate(self, templateId: str) -> Optional[AutomationTemplate]:
filtered = getRecordsetWithRBAC(self.db, AutomationTemplate, self.currentUser, recordFilter={"id": templateId})
return AutomationTemplate(**filtered[0]) if filtered else None
def createAutomationTemplate(self, templateData: Dict) -> AutomationTemplate:
if not self.checkRbacPermission(AutomationTemplate, "create"):
raise ValueError("No permission to create template")
simpleFields, _ = self._separateObjectFields(AutomationTemplate, templateData)
created = self.db.recordCreate(AutomationTemplate, simpleFields)
return AutomationTemplate(**created)
def updateAutomationTemplate(self, templateId: str, templateData: Dict) -> AutomationTemplate:
existing = self.getAutomationTemplate(templateId)
if not existing:
raise ValueError("Template not found")
if not self.checkRbacPermission(AutomationTemplate, "update", templateId):
raise ValueError("No permission to update")
simpleFields, _ = self._separateObjectFields(AutomationTemplate, templateData)
updated = self.db.recordModify(AutomationTemplate, templateId, simpleFields)
return AutomationTemplate(**updated)
def deleteAutomationTemplate(self, templateId: str) -> bool:
existing = self.getAutomationTemplate(templateId)
if not existing:
return False
if not self.checkRbacPermission(AutomationTemplate, "delete", templateId):
raise ValueError("No permission to delete")
self.db.recordDelete(AutomationTemplate, templateId)
return True
```
---
## 4. Bootstrap: Template-Seed
### 4.1 Neue Funktion in interfaceBootstrap.py
```python
def initAutomationTemplates(db: DatabaseConnector) -> None:
"""
Seed initial automation templates from subAutomationTemplates.py.
Only runs if no templates exist yet (bootstrap).
Creates templates with _createdBy = admin user (SysAdmin privilege).
"""
from modules.features.automation.subAutomationTemplates import AUTOMATION_TEMPLATES
from modules.features.automation.datamodelFeatureAutomation import AutomationTemplate
# Check if templates already exist
existing = db.getRecordset(AutomationTemplate)
if existing:
logger.info(f"Automation templates already seeded ({len(existing)} templates)")
return
# Get admin user ID for _createdBy
adminUsers = db.getRecordset(UserInDB, {"email": APP_CONFIG.ADMIN_EMAIL})
adminUserId = adminUsers[0]["id"] if adminUsers else None
templates = AUTOMATION_TEMPLATES.get("sets", [])
for i, templateSet in enumerate(templates):
templateContent = templateSet.get("template", {})
overview = templateContent.get("overview", f"Template {i+1}")
# Create multilingual label from overview (German as primary since current templates are German)
label = {"en": overview, "de": overview}
# Create template WITHOUT parameters (no sharp values)
templateData = {
"label": label,
"overview": {"en": overview, "de": overview},
"template": json.dumps(templateContent), # Only template JSON with {{KEY:...}}
}
# Set _createdBy to admin for bootstrap
if adminUserId:
templateData["_createdBy"] = adminUserId
db.recordCreate(AutomationTemplate, templateData)
logger.info(f"Created automation template: {overview}")
logger.info(f"Seeded {len(templates)} automation templates")
```
### 4.2 In initBootstrap() aufrufen
```python
def initBootstrap(db: DatabaseConnector) -> None:
# ... existing code ...
# Seed automation templates (after admin user exists)
initAutomationTemplates(db)
logger.info("System bootstrap completed")
```
---
## 5. Actions-Katalog Endpoint
### 5.1 Neuer Endpoint: GET /api/automations/actions
```python
from modules.workflows.processing.shared.methodDiscovery import discoverMethods, methods
@router.get("/actions")
async def get_available_actions(
request: Request,
context: RequestContext = Depends(getRequestContext)
):
"""
Get available workflow actions filtered by RBAC.
Returns action definitions with parameters and example JSON snippets.
"""
# Ensure methods are discovered
if not methods:
# Need a serviceCenter with current user for RBAC filtering
# This requires a lightweight serviceCenter or direct method iteration
pass
actionsList = []
for methodName, methodInfo in methods.items():
# Skip duplicate short names (e.g., "ai" and "AiMethod" are same)
if methodName != methodName.lower():
continue
methodInstance = methodInfo.get("instance")
if not methodInstance:
continue
for actionName, actionDef in methodInstance._actions.items():
actionId = actionDef.actionId
# RBAC check: user needs view permission on this action (RESOURCE context)
permissions = context.rbac.getUserPermissions(
user=context.user,
context=AccessRuleContext.RESOURCE,
item=actionId
)
if not permissions.view:
continue
# Build action info from WorkflowActionDefinition
actionInfo = {
"method": methodName,
"action": actionName,
"actionId": actionId,
"description": actionDef.description,
"category": actionDef.category,
"parameters": []
}
# Add parameters from WorkflowActionParameter
for paramName, paramDef in actionDef.parameters.items():
actionInfo["parameters"].append({
"name": paramName,
"type": paramDef.type,
"frontendType": paramDef.frontendType.value if paramDef.frontendType else "text",
"required": paramDef.required,
"default": paramDef.default,
"description": paramDef.description,
"frontendOptions": paramDef.frontendOptions,
})
# Build example JSON snippet for copy/paste
exampleParams = {}
for paramName, paramDef in actionDef.parameters.items():
if paramDef.required:
exampleParams[paramName] = f"{{{{KEY:{paramName}}}}}"
else:
exampleParams[paramName] = paramDef.default or f"{{{{KEY:{paramName}}}}}"
actionInfo["exampleJson"] = {
"execMethod": methodName,
"execAction": actionName,
"execParameters": exampleParams,
"execResultLabel": f"{methodName}_{actionName}_result"
}
actionsList.append(actionInfo)
return JSONResponse(content={"actions": actionsList})
```
---
## 6. Label in User-Sprache bei Definition-Erstellung
### 6.1 Bei "Aus Template erstellen" (Frontend ruft Backend)
Wenn das Frontend eine neue AutomationDefinition aus einem Template erstellt, sendet es:
- `template` (JSON von AutomationTemplate.template)
- `label` (aus AutomationTemplate.label in User-Sprache extrahiert)
**Backend-seitig** (falls Backend den Label-Extract macht):
```python
def createAutomationFromTemplate(self, templateId: str, userLanguage: str = "en") -> AutomationDefinition:
"""Create a new AutomationDefinition from a template, label in user's language."""
template = self.getAutomationTemplate(templateId)
if not template:
raise ValueError("Template not found")
# Extract label in user's language
labelMulti = template.label # TextMultilingual object
if hasattr(labelMulti, 'get_text'):
label = labelMulti.get_text(userLanguage)
elif isinstance(labelMulti, dict):
label = labelMulti.get(userLanguage) or labelMulti.get("en", "New Automation")
else:
label = str(labelMulti)
# Create definition with template content
definitionData = {
"label": label,
"template": template.template, # Copy template JSON
"placeholders": {}, # Empty - user fills in later
"schedule": "0 22 * * *", # Default schedule
"active": False,
}
return self.createAutomationDefinition(definitionData)
```
**Alternative:** Frontend extrahiert Label selbst und sendet direkt an `createAutomationDefinition`.
---
## 7. Zusammenfassung der Änderungen
| Datei | Änderung |
|-------|----------|
| `datamodelFeatureAutomation.py` | Neues Modell `AutomationTemplate` mit `TextMultilingual` für label/overview |
| `interfaceRbac.py` | TABLE_NAMESPACE_MAP erweitern: `"AutomationTemplate": "automation"` |
| `interfaceBootstrap.py` | RBAC-Regeln für AutomationTemplate (MY); Bootstrap-Seed `initAutomationTemplates()` |
| `interfaceDbChat.py` | CRUD-Methoden für AutomationTemplate (analog AutomationDefinition) |
| `routeFeatureAutomation.py` | Endpoints: GET/POST/PUT/DELETE `/api/automation-templates/*`, GET `/api/automations/actions` |
| `mainAutomation.py` | UI-Object `ui.feature.automation.templates` bereits vorhanden |
---
## 8. Abhängigkeiten & Reihenfolge
1. **Datamodel** erstellen (AutomationTemplate)
2. **RBAC** in interfaceRbac.py und interfaceBootstrap.py
3. **Interface-Methoden** in interfaceDbChat.py
4. **Routes** in routeFeatureAutomation.py
5. **Bootstrap-Seed** in interfaceBootstrap.py (nach DB-Migration)
6. **Actions-Endpoint** als letzte Erweiterung

View file

@ -147,7 +147,7 @@ async def _callWithContentParts(self, request: AiCallRequest) -> AiCallResponse:
return AiCallResponse(
content=mergedContent,
modelName="multiple",
priceUsd=sum(r.priceUsd for r in allResults),
priceCHF=sum(r.priceCHF for r in allResults),
processingTime=sum(r.processingTime for r in allResults),
bytesSent=sum(r.bytesSent for r in allResults),
bytesReceived=sum(r.bytesReceived for r in allResults),
@ -185,7 +185,7 @@ async def _processContentPartWithFallback(self, contentPart: ContentPart, prompt
# Merge chunk results
mergedContent = self._mergeChunkResults(chunkResults)
totalPrice = sum(r.priceUsd for r in chunkResults)
totalPrice = sum(r.priceCHF for r in chunkResults)
totalTime = sum(r.processingTime for r in chunkResults)
totalBytesSent = sum(r.bytesSent for r in chunkResults)
totalBytesReceived = sum(r.bytesReceived for r in chunkResults)
@ -195,7 +195,7 @@ async def _processContentPartWithFallback(self, contentPart: ContentPart, prompt
return AiCallResponse(
content=mergedContent,
modelName=model.name,
priceUsd=totalPrice,
priceCHF=totalPrice,
processingTime=totalTime,
bytesSent=totalBytesSent,
bytesReceived=totalBytesReceived,
@ -316,7 +316,7 @@ async def _processPartsWithMapping(self, extractionResult: List[ContentExtracted
"resultSize": len(response.content),
"typeGroup": part.typeGroup,
"modelName": response.modelName,
"priceUsd": response.priceUsd
"priceCHF": response.priceCHF
}
)

View file

@ -20,7 +20,7 @@ Owner: Workflow/Data Layer
- Example values: "action.outlook.readMails", "ai.process.document.name"
- Add: engine: str
- Example values: "ai.anthropic.35", "ai.tavily.basic", "renderer.docx"
- Add: priceUsd: float (calculated price in USD for the operation)
- Add: priceCHF: float (calculated price in USD for the operation)
- ChatWorkflow
- stats: change from Optional[ChatStat] to List[ChatStat] (default [])
@ -52,7 +52,7 @@ DB logic:
## Service Layer Changes (modules/services/serviceWorkflow/mainServiceWorkflow.py)
- Replace storeWorkflowStat(workflow, statData) to:
- Coerce statData to ChatStat fields (process, engine, priceUsd, bytesSent, bytesReceived, processingTime, errorCount) --> to handover pydantic model (with id empty), not the single values
- Coerce statData to ChatStat fields (process, engine, priceCHF, bytesSent, bytesReceived, processingTime, errorCount) --> to handover pydantic model (with id empty), not the single values
- Set id and workflowId
- Persist via interfaceDbChat.createWorkflowStat
- Append to workflow.stats in memory
@ -65,10 +65,10 @@ DB logic:
## Instrumentation Points (where to emit stats)
1) AI Calls (modules/interfaces/interfaceAiObjects.py)
- Each AI call measures processing time, calculates priceUsd, and tracks bytes sent/received
- Returns standardized AiCallResponse with priceUsd, processingTime, bytesSent, bytesReceived, errorCount included (to adapt pydantic model)
- Each AI call measures processing time, calculates priceCHF, and tracks bytes sent/received
- Returns standardized AiCallResponse with priceCHF, processingTime, bytesSent, bytesReceived, errorCount included (to adapt pydantic model)
- All AI functions (call, callImage, generateImage, webQuery, etc.) return this standardized response
- Model-specific pricing functions calculate priceUsd based on (processingTime, bytesSent, bytesReceived)
- Model-specific pricing functions calculate priceCHF based on (processingTime, bytesSent, bytesReceived)
2) Service Layer (modules/services/serviceWorkflow/mainServiceWorkflow.py)
- Receives AiCallResponse from AI interface
@ -78,7 +78,7 @@ DB logic:
3) Extraction/Generation Services
- services/serviceExtraction/mainServiceExtraction.py and related extractors
- services/serviceGeneration/* (document rendering/generation)
- Emit ChatStat per completed operation with process, engine, processingTime, bytes, priceUsd, etc.
- Emit ChatStat per completed operation with process, engine, processingTime, bytes, priceCHF, etc.
4) Workflow-level summaries (optional)
- At workflow completion, compute a summary stat (aggregated bytes/costs) if desired
@ -90,7 +90,7 @@ DB logic:
Update AiCallResponse to include standardized stats fields:
- Remove: usedTokens, costEstimate
- Add: priceUsd: float (calculated price in USD)
- Add: priceCHF: float (calculated price in USD)
- Add: processingTime: float (duration in seconds)
- Add: bytesSent: int (input data size in bytes)
- Add: bytesReceived: int (output data size in bytes)
@ -100,7 +100,7 @@ Update AiCallResponse to include standardized stats fields:
Each AI model should implement a pricing calculation function that takes the operation parameters and returns the cost in USD:
- Function signature: `_calculatePriceUsd(processingTime: float, bytesSent: int, bytesReceived: int) -> float`
- Function signature: `_calculatepriceCHF(processingTime: float, bytesSent: int, bytesReceived: int) -> float`
- Parameters:
- `processingTime`: Duration of the operation in seconds
- `bytesSent`: Size of input data in bytes
@ -136,7 +136,7 @@ Example implementations:
### Phase 1: Core Data Model Changes
1. **Update ChatStat model** (modules/datamodels/datamodelChat.py)
- Remove: successRate, tokenCount, tokenPriceUnit, tokenPriceAmount, messageId
- Add: process: str, engine: str, priceUsd: float
- Add: process: str, engine: str, priceCHF: float
- Keep: id, workflowId, processingTime, bytesSent, bytesReceived, errorCount
2. **Update ChatWorkflow model** (modules/datamodels/datamodelChat.py)
@ -148,7 +148,7 @@ Example implementations:
4. **Update AiCallResponse model** (modules/datamodels/datamodelAi.py)
- Remove: usedTokens, costEstimate
- Add: priceUsd: float, processingTime: float, bytesSent: int, bytesReceived: int, errorCount: int
- Add: priceCHF: float, processingTime: float, bytesSent: int, bytesReceived: int, errorCount: int
### Phase 2: Database Interface Changes
5. **Update interfaceDbChatObjects.py**
@ -159,14 +159,14 @@ Example implementations:
### Phase 3: AI Interface Layer Changes
6. **Add model-specific pricing functions** (modules/interfaces/interfaceAiObjects.py)
- Add _calculatePriceUsd() method to each model in aiModels registry
- Add _calculatepriceCHF() method to each model in aiModels registry
- Implement pricing logic for each connector type (OpenAI, Anthropic, Perplexity, Tavily)
7. **Update all AI call methods** (modules/interfaces/interfaceAiObjects.py)
- Add timing measurement (start/end timestamps)
- Calculate bytesSent (input data size)
- Calculate bytesReceived (output data size)
- Call model-specific _calculatePriceUsd()
- Call model-specific _calculatepriceCHF()
- Return standardized AiCallResponse with all stats fields
8. **Update specific AI methods:**
@ -207,7 +207,7 @@ Example implementations:
- Update UI polling to handle stats array
15. **Add comprehensive logging**
- Debug logs for each stat emission (process, engine, bytes, priceUsd)
- Debug logs for each stat emission (process, engine, bytes, priceCHF)
- Error handling for stats emission failures
### Phase 7: Cleanup and Validation
@ -231,7 +231,7 @@ Example implementations:
- **Centralized measurement**: AI interface layer handles all timing and byte counting, ensuring consistency.
- **Standardized responses**: All AI calls return AiCallResponse with complete stats data.
- **Model-specific pricing**: Each model implements `_calculatePriceUsd()` with its own pricing strategy.
- **Model-specific pricing**: Each model implements `_calculatepriceCHF()` with its own pricing strategy.
- **Service layer simplicity**: Service layer just creates ChatStat objects from AiCallResponse data.
- **Append-only stats**: Simplifies auditing and invoicing; no implicit overwrites.
- **Workflow-scoped stats**: All stats attached to workflow, not messages; UI gets complete history.