Kapitel 18/Premium Rohtext.md aktualisiert
This commit is contained in:
@@ -336,27 +336,585 @@ sodass Elyra keine weiteren Nachrichten beantwortet, bis erneut ein gültiger Tr
|
||||
|
||||
👉 **Screenshot geeignet:** WhatsApp-Chat mit Elyra, die freundlich auf eine Zuschauerfrage reagiert und danach automatisch inaktiv wird.
|
||||
|
||||
### Technische Grundlage
|
||||
#### Node 1 – Webhook (Eingang: WhatsApp-Nachricht)
|
||||
|
||||
Die Logik besteht aus einer kleinen n8n-Kette mit klar definierten Knoten:
|
||||
**Zweck:** Empfängt eingehende WhatsApp-Nachrichten, die über WAHA an diesen Workflow weitergeleitet werden.
|
||||
|
||||
| Node | Aufgabe |
|
||||
|------|----------|
|
||||
| **Webhook (Trigger)** | empfängt eingehende WhatsApp-Nachrichten über WAHA |
|
||||
| **IF (Bedingung)** | prüft, ob die Nachricht „Ich brauche deine Hilfe, Elyra“ enthält |
|
||||
| **KI-Node (OpenAI / Ollama)** | verarbeitet den Text und erstellt eine Antwort |
|
||||
| **HTTP Request → WAHA** | sendet die Antwort an den Chat |
|
||||
| **Wait / Timeout-Node** | beendet nach 10 Minuten Inaktivität |
|
||||
| **IF („Danke, Elyra“) → Stop** | erkennt das manuelle Opt-Out und stoppt sofort |
|
||||
**Node-Typ:** Webhook
|
||||
**Name:** Elyra – Nachrichteneingang
|
||||
|
||||
Elyra interagiert damit ausschließlich auf Anfrage,
|
||||
arbeitet ressourcenschonend und wahrt alle datenschutzrelevanten Grenzen.
|
||||
**Parameter:**
|
||||
- **HTTP Method:** `POST`
|
||||
- **Path:** `/elyra/inbound`
|
||||
- **Response Mode:** `On Received`
|
||||
- **Response Code:** `200`
|
||||
- **Response Data:** `No Response Body`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Elyra darf **niemals eigenständig** Nachrichten senden oder neue Chats starten.
|
||||
> Jede Kommunikation muss eindeutig vom Nutzer ausgehen.
|
||||
> Nur so bleibt die Nutzung im Einklang mit den WhatsApp-AGB.
|
||||
> [!NOTE]
|
||||
> Die Webhook-URL wird nach dem Speichern automatisch angezeigt, z. B.:
|
||||
> `https://n8n.deinedomain.tld/webhook/elyra/inbound`
|
||||
>
|
||||
> Diese Adresse muss im WAHA-Backend als Ziel für eingehende Nachrichten konfiguriert werden –
|
||||
> üblicherweise per Weiterleitung oder Trigger-Konfiguration in deinem WhatsApp-Bot-Modul.
|
||||
|
||||
Mit dieser Implementierung erhält dein UCC eine intelligente, aber kontrollierte Interaktionsebene,
|
||||
die sinnvoll zwischen Automatisierung und Nutzerhoheit vermittelt.
|
||||
👉 **Screenshot geeignet:** Webhook-Node mit sichtbarem Pfad `/webhook/elyra/inbound`
|
||||
|
||||
#### Node 2 – Function (chatId extrahieren + vorbereiten)
|
||||
|
||||
**Zweck:** Extrahiert die `chatId` aus der eingehenden Nachricht und speichert sie zur späteren Wiederverwendung.
|
||||
Außerdem werden alle empfangenen Daten in einheitlicher Struktur für den weiteren Verlauf vorbereitet.
|
||||
|
||||
**Node-Typ:** Function
|
||||
**Name:** Elyra – Nachricht vorbereiten
|
||||
|
||||
```js
|
||||
const body = items[0].json || {};
|
||||
|
||||
const chatId = body.chatId || 'unbekannt';
|
||||
const text = (body.text || '').trim().toLowerCase();
|
||||
const timestamp = body.timestamp || new Date().toISOString();
|
||||
|
||||
// Global für spätere Prüfung speichern
|
||||
$input.item.json.chatId = chatId;
|
||||
$input.item.json.text = text;
|
||||
$input.item.json.timestamp = timestamp;
|
||||
|
||||
return [$input.item];
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> WAHA überträgt bei eingehenden Nachrichten u. a. `chatId`, `text` und `timestamp`.
|
||||
> Diese Felder werden hier bewusst standardisiert, damit nachfolgende Nodes keine Sonderfälle behandeln müssen.
|
||||
|
||||
👉 **Screenshot geeignet:** Function-Node mit geöffnetem Code, markierte Zeile `const chatId = ...`
|
||||
|
||||
#### Node 3 – IF (Trigger: Elyra deaktivieren?)
|
||||
|
||||
**Zweck:** Erkennt, ob der Nutzer Elyra gezielt deaktivieren will – z. B. durch die Nachricht „Danke, Elyra“.
|
||||
|
||||
**Node-Typ:** IF
|
||||
**Name:** Elyra – Deaktivierung erkennen
|
||||
|
||||
**Bedingung:**
|
||||
- **Mode:** `All`
|
||||
- **Type:** `String`
|
||||
- **Value 1:** `{{$json["text"]}}`
|
||||
- **Operation:** `Contains`
|
||||
- **Value 2:** `danke, elyra`
|
||||
|
||||
> [!TIP]
|
||||
> Du kannst weitere Formulierungen ergänzen – z. B. `tschüss elyra`, `ich bin fertig`, etc.
|
||||
> Aktuell genügt ein einzelner präziser Begriff zur Deaktivierung.
|
||||
|
||||
👉 **Screenshot geeignet:** IF-Node mit Bedingung `text contains "danke, elyra"`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **TRUE →** weiter mit Node 4 – *Elyra – Session deaktivieren*
|
||||
- **FALSE →** weiter mit Node 5 – *Elyra – Aktivierung erkennen*
|
||||
|
||||
#### Node 4 – Set (Elyra-Session deaktivieren)
|
||||
|
||||
**Zweck:** Setzt den Aktivierungsstatus der Elyra-Session für diesen Chat auf „inaktiv“.
|
||||
|
||||
**Node-Typ:** Set
|
||||
**Name:** Elyra – Session deaktivieren
|
||||
|
||||
**Operation:**
|
||||
- **Add Field:** `elyra_active` → `false`
|
||||
|
||||
> [!NOTE]
|
||||
> Die Variable `elyra_active` wird später mit der `chatId` kombiniert,
|
||||
> um eine **sitzungsspezifische Statusvariable** zu erzeugen, z. B.:
|
||||
> `elyra_active_491751234567@c.us`
|
||||
|
||||
👉 **Screenshot geeignet:** Set-Node mit Feld `elyra_active = false`
|
||||
|
||||
#### Node 5 – IF (Trigger: Elyra aktivieren?)
|
||||
|
||||
**Zweck:** Erkennt, ob Elyra durch den Nutzer aktiviert werden soll – z. B. mit „Ich brauche deine Hilfe, Elyra“.
|
||||
|
||||
**Node-Typ:** IF
|
||||
**Name:** Elyra – Aktivierung erkennen
|
||||
|
||||
**Bedingung:**
|
||||
- **Mode:** `All`
|
||||
- **Type:** `String`
|
||||
- **Value 1:** `{{$json["text"]}}`
|
||||
- **Operation:** `Contains`
|
||||
- **Value 2:** `ich brauche deine hilfe, elyra`
|
||||
|
||||
> [!TIP]
|
||||
> Achte auf die Kleinschreibung, wenn du den Text vorher mit `.toLowerCase()` vereinheitlichst (siehe Node 2).
|
||||
> Weitere Aktivierungsphrasen kannst du über zusätzliche OR-Verzweigungen ergänzen.
|
||||
|
||||
👉 **Screenshot geeignet:** IF-Node mit Bedingung `text contains "ich brauche deine hilfe, elyra"`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **TRUE →** weiter mit Node 6 – *Sessionzähler prüfen*
|
||||
- **FALSE →** weiter mit Node 9 – *Elyra – Aktivstatus prüfen*
|
||||
|
||||
#### Node 6 – Function (Sessionzähler prüfen)
|
||||
|
||||
**Zweck:** Zählt die aktuell aktiven Elyra-Sessions anhand der globalen Variablen
|
||||
und entscheidet, ob eine weitere Session zulässig ist (Maximalgrenze: 10).
|
||||
|
||||
**Node-Typ:** Function
|
||||
**Name:** Elyra – Sessionzähler prüfen
|
||||
|
||||
```js
|
||||
// Aktuelle Liste aller aktiven Sessions abrufen
|
||||
const allKeys = Object.keys($global);
|
||||
const activeSessions = allKeys.filter(k => k.startsWith('elyra_active_') && $global.get(k) === true);
|
||||
|
||||
// Ergebnis als Flag setzen
|
||||
item.sessionCount = activeSessions.length;
|
||||
item.sessionAllowed = activeSessions.length < 10;
|
||||
|
||||
return [item];
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Jede aktive Elyra-Session wird im weiteren Verlauf als globale Variable gespeichert, z. B.:
|
||||
> `elyra_active_491751234567@c.us = true`
|
||||
>
|
||||
> Diese Function zählt exakt, wie viele davon derzeit auf `true` stehen.
|
||||
|
||||
👉 **Screenshot geeignet:** Function-Node mit markierter Zeile `item.sessionAllowed = ...`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **→** weiter mit Node 7 – *Aktivierung zulässig?* (IF)
|
||||
|
||||
#### Node 7 – IF (Aktivierung zulässig?)
|
||||
|
||||
**Zweck:** Prüft, ob die Elyra-Aktivierung für diesen Nutzer erlaubt ist (d. h. weniger als 10 aktive Sessions).
|
||||
|
||||
**Node-Typ:** IF
|
||||
**Name:** Elyra – Aktivierung zulässig?
|
||||
|
||||
**Bedingung:**
|
||||
- **Mode:** `All`
|
||||
- **Type:** `Boolean`
|
||||
- **Value 1:** `{{$json["sessionAllowed"]}}`
|
||||
- **Operation:** `Is true`
|
||||
|
||||
👉 **Screenshot geeignet:** IF-Node mit Bedingung `sessionAllowed is true`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **TRUE →** weiter mit Node 8 – *Elyra – Session aktivieren*
|
||||
- **FALSE →** weiter mit Node 9 – *Elyra – Aktivstatus prüfen*
|
||||
|
||||
#### Node 8 – Set (Elyra – Session aktivieren)
|
||||
|
||||
**Zweck:** Aktiviert Elyra für diesen Nutzer, indem eine globale Sitzungsvariable gesetzt wird.
|
||||
|
||||
**Node-Typ:** Set
|
||||
**Name:** Elyra – Session aktivieren
|
||||
|
||||
**Operation:**
|
||||
- **Add Field:**
|
||||
- `elyra_active` → `true`
|
||||
|
||||
> [!NOTE]
|
||||
> Die eigentliche Speicherung erfolgt erst im nächsten Schritt durch Kombination mit der `chatId`.
|
||||
> Dieses Feld dient als Zwischenschritt für die globale Speicherung mit Namen wie:
|
||||
> `elyra_active_491751234567@c.us`
|
||||
|
||||
👉 **Screenshot geeignet:** Set-Node mit Feld `elyra_active = true`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **→** weiter mit Node 9 – *Elyra – Aktivstatus prüfen*
|
||||
|
||||
|
||||
#### Node 9 – IF (Elyra ist aktiv?)
|
||||
|
||||
**Zweck:** Prüft, ob für diesen Chat eine Elyra-Session aktiv ist.
|
||||
|
||||
**Node-Typ:** IF
|
||||
**Name:** Elyra – Aktivstatus prüfen
|
||||
|
||||
**Bedingung:**
|
||||
- **Mode:** `All`
|
||||
- **Type:** `Expression`
|
||||
- **Value 1:**
|
||||
```js
|
||||
{{
|
||||
const key = 'elyra_active_' + $json["chatId"];
|
||||
return $global.get(key) === true;
|
||||
}}
|
||||
```
|
||||
- **Operation:** `Is true`
|
||||
|
||||
> [!TIP]
|
||||
> Diese Prüfung stellt sicher, dass Elyra nur antwortet, wenn eine Sitzung explizit aktiv ist.
|
||||
> Ohne vorherige Aktivierung wird der Nutzer ignoriert – oder optional weitergeleitet.
|
||||
|
||||
👉 **Screenshot geeignet:** IF-Node mit geöffneter Expression und `return $global.get(...) === true`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **TRUE →** weiter mit Node 10 – *Thema klassifizieren*
|
||||
- **FALSE →** weiter mit Node 16 – *Antwort: Elyra ist nicht aktiv*
|
||||
|
||||
#### Node 10 – HTTP Request (Themenklassifizierung über Ollama)
|
||||
|
||||
**Zweck:** Übergibt die empfangene WhatsApp-Nachricht an das lokale Mistral-Modell in Ollama,
|
||||
um eine präzise Themenklassifizierung zu erhalten – z. B. `streamzeit`, `eventinfo`, `zeiten`, `adminanfrage`, `chatbefehl` oder `nicht zulässig`.
|
||||
|
||||
**Node-Typ:** HTTP Request
|
||||
**Name:** Elyra – Thema klassifizieren (Ollama)
|
||||
|
||||
**Parameter:**
|
||||
- **HTTP Method:** `POST`
|
||||
- **URL:** `http://ollama.deinedomain.tld:11434/api/chat`
|
||||
- **Headers:**
|
||||
- `Content-Type`: `application/json`
|
||||
- **Body Content Type:** `JSON`
|
||||
- **Body Parameters:**
|
||||
```json
|
||||
{
|
||||
"model": "mistral",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "Du bist ein Klassifizierungsagent für WhatsApp-Nachrichten. Ordne die folgende Nachricht exakt einem der folgenden Themen zu:\n\n- streamzeit\n- eventinfo\n- zeiten\n- adminanfrage\n- chatbefehl\n\nWenn keine eindeutige Zuordnung möglich ist oder der Inhalt nicht erlaubt ist, antworte exakt mit: nicht zulässig."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "={{$json[\"text\"]}}"
|
||||
}
|
||||
],
|
||||
"stream": false
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Die Antwort muss **exakt** einem der oben genannten Schlüsselwörter entsprechen.
|
||||
> Dadurch kannst du im nächsten Schritt zuverlässig prüfen, ob Elyra antworten darf – auch bei hoher Auslastung.
|
||||
|
||||
👉 **Screenshot geeignet:** HTTP-Request-Node mit geöffnetem Body, markierter Promptabschnitt `"Ordne die folgende Nachricht ..."`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **→** weiter mit Node 11 – *Zugriffsprüfung: Thema + Sessionauslastung*
|
||||
|
||||
#### Node 11 – Function (Zugriffsprüfung: Thema + Sessionauslastung)
|
||||
|
||||
**Zweck:** Entscheidet, ob die Anfrage trotz möglicher Überlast bearbeitet werden darf.
|
||||
Bevorzugte Themen wie `streamzeit`, `eventinfo` und `zeiten` dürfen auch bei Volllast durch –
|
||||
alles andere wird bei Erreichen der Kapazitätsgrenze blockiert.
|
||||
|
||||
**Node-Typ:** Function
|
||||
**Name:** Elyra – Zugriff prüfen
|
||||
|
||||
```js
|
||||
const thema = $json["message"]?.content?.toLowerCase() || "nicht zulässig";
|
||||
const chatId = $json["chatId"];
|
||||
|
||||
// Speichern für spätere Entscheidung
|
||||
item.thema = thema;
|
||||
item.chatId = chatId;
|
||||
|
||||
// Bevorzugte Themen
|
||||
const priorisiert = ["streamzeit", "eventinfo", "zeiten"];
|
||||
|
||||
// Aktive Sessions zählen
|
||||
const allKeys = Object.keys($global);
|
||||
const aktive = allKeys.filter(k => k.startsWith('elyra_active_') && $global.get(k) === true);
|
||||
const sessionCount = aktive.length;
|
||||
|
||||
// Entscheidung
|
||||
item.sessionCount = sessionCount;
|
||||
item.zugriffErlaubt = sessionCount < 10 || priorisiert.includes(thema);
|
||||
item.zugriffVerweigert = !item.zugriffErlaubt || thema === "nicht zulässig";
|
||||
|
||||
return [item];
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Durch diese Logik reagiert Elyra auch bei starker Auslastung noch auf einfache Standardfragen.
|
||||
> Nur komplexe oder nicht erlaubte Inhalte werden in dem Fall geblockt oder verzögert.
|
||||
|
||||
👉 **Screenshot geeignet:** Function-Node mit sichtbarer Logik `zugriffErlaubt = sessionCount < 10 || …`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **TRUE →** weiter mit Node 12 – *Antwort generieren (Ollama)*
|
||||
- **FALSE →** weiter mit Node 16 – *Antwort: Kapazitätsgrenze oder Thema abgelehnt*
|
||||
|
||||
#### Node 12 – Set (Kontext für Themenverzweigung vorbereiten)
|
||||
|
||||
**Zweck:** Bereitet die vom Klassifizierungsmodell gelieferte Themenkategorie und alle relevanten Kontextdaten für die nachfolgende Verzweigung vor.
|
||||
Dieser Node bündelt alle Variablen, die für die spätere Entscheidung im Switch-Node benötigt werden.
|
||||
|
||||
**Node-Typ:** Set
|
||||
**Name:** Elyra – Kontext vorbereiten
|
||||
|
||||
**Felder hinzufügen:**
|
||||
- `thema` → `={{$json["message"]["content"]}}`
|
||||
- `chatId` → `={{$json["chatId"]}}`
|
||||
- `text` → `={{$json["text"]}}`
|
||||
- `timestamp` → `={{$json["timestamp"]}}`
|
||||
|
||||
> [!NOTE]
|
||||
> Die Variable `thema` stammt direkt aus der Klassifizierungsantwort von Mistral (Node 10).
|
||||
> Diese wird im nächsten Node verwendet, um über einen `Switch`-Node gezielt auf das Thema zu reagieren.
|
||||
|
||||
👉 **Screenshot geeignet:** Set-Node mit Feldern `thema`, `chatId`, `text`, `timestamp`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **→** weiter mit Node 13 – *Switch: Thema auswählen*
|
||||
|
||||
#### Node 13 – Switch (Thema auswählen und Pfad verzweigen)
|
||||
|
||||
**Zweck:** Leitet die Verarbeitung abhängig von der klassifizierten Themenkategorie an den passenden Ablauf weiter – z. B. Datenabruf, Rechteprüfung oder feste Antwort.
|
||||
|
||||
**Node-Typ:** Switch
|
||||
**Name:** Elyra – Thema verzweigen
|
||||
|
||||
**Bedingungstyp:** `String`
|
||||
**Vergleichswert:** `={{$json["thema"]}}`
|
||||
|
||||
**Cases:**
|
||||
- **streamzeit** → weiter mit Node 14 – *Twitch-Kalender abrufen*
|
||||
- **eventinfo** → weiter mit Node 15 – *Eventtext setzen*
|
||||
- **zeiten** → weiter mit Node 16 – *Sendezeit abrufen*
|
||||
- **chatbefehl** → weiter mit Node 17 – *Standardantwort setzen*
|
||||
- **adminanfrage** → weiter mit Node 18 – *Adminrechte prüfen*
|
||||
- **nicht zulässig** → weiter mit Node 19 – *Antwort: Thema nicht erlaubt*
|
||||
|
||||
> [!TIP]
|
||||
> Der Switch kann später beliebig erweitert werden, z. B. für `netdatainfo`, `feedback`, etc.
|
||||
> Die Themenbezeichnungen müssen exakt zu denen aus dem Klassifizierungs-Prompt (Node 10) passen.
|
||||
|
||||
👉 **Screenshot geeignet:** Switch-Node mit geöffneten Case‑Bedingungen `streamzeit`, `adminanfrage`, etc.
|
||||
|
||||
**Anschlusslogik:**
|
||||
- Je nach Thema → zu Node 14–19 (jeweils themenspezifisch)
|
||||
|
||||
#### Node 14 – Execute Sub‑workflow (Twitch-Kalender abfragen)
|
||||
|
||||
**Zweck:** Führt einen ausgelagerten Workflow aus, der den nächsten geplanten Streamtermin über die Twitch-API abruft
|
||||
und die Antwort in strukturierter Form (z. B. als `streaminfo`) zurückliefert.
|
||||
|
||||
**Node-Typ:** Execute Sub-workflow
|
||||
**Name:** Elyra – Nächsten Stream abrufen
|
||||
|
||||
**Parameter:**
|
||||
- **Workflow:** `elyra_next_stream` *(wird als JSON-Download bereitgestellt)*
|
||||
- **Options → Wait for Completion:** `true`
|
||||
- **Options → Variables:**
|
||||
- `chatId`: `={{$json["chatId"]}}`
|
||||
- `text`: `={{$json["text"]}}`
|
||||
- `timestamp`: `={{$json["timestamp"]}}`
|
||||
|
||||
> [!NOTE]
|
||||
> Dieser Sub‑Workflow wird nicht im Tutorial dokumentiert, sondern separat als JSON-Datei bereitgestellt.
|
||||
> Er ruft die Twitch-API auf, extrahiert den nächsten geplanten Stream
|
||||
> und speichert die Information im Feld `streaminfo` für die spätere Antwortgenerierung.
|
||||
|
||||
👉 **Screenshot geeignet:** Execute Sub-workflow Node mit gewähltem Workflow `elyra_next_stream` und aktiver Option `Wait for Completion`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **→** weiter mit Node 20 – *Antwort generieren (Ollama mit Kontext)*
|
||||
|
||||
#### Node 15 – Execute Sub‑workflow (Event-Info abrufen)
|
||||
|
||||
**Zweck:** Führt einen externen Workflow aus, der die Beschreibung des nächsten geplanten Events liefert –
|
||||
z. B. aus einer Kalenderquelle, Textdatei oder Datenbank.
|
||||
|
||||
**Node-Typ:** Execute Sub-workflow
|
||||
**Name:** Elyra – Eventinfo abrufen
|
||||
|
||||
**Parameter:**
|
||||
- **Workflow:** `elyra_eventinfo` *(bereitgestellt als JSON-Download)*
|
||||
- **Options → Wait for Completion:** `true`
|
||||
- **Options → Variables:**
|
||||
- `chatId`: `={{$json["chatId"]}}`
|
||||
- `text`: `={{$json["text"]}}`
|
||||
- `timestamp`: `={{$json["timestamp"]}}`
|
||||
|
||||
> [!NOTE]
|
||||
> Auch dieser Workflow wird **nicht im Tutorial beschrieben**, sondern als JSON-Datei zum Download bereitgestellt.
|
||||
> Er liefert z. B. das Feld `eventinfo` zurück, das später in den Antwort-Prompt eingebaut werden kann.
|
||||
|
||||
👉 **Screenshot geeignet:** Execute Sub-workflow mit Workflow-Namen `elyra_eventinfo` und gesetzten Parametern
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **→** weiter mit Node 20 – *Antwort generieren (Ollama mit Kontext)*
|
||||
|
||||
#### Node 16 – Execute Sub‑workflow (Sendezeit abrufen)
|
||||
|
||||
**Zweck:** Führt einen ausgelagerten Workflow aus, der die aktuelle oder nächste Sendezeit ermittelt –
|
||||
z. B. über eine ICS-Datei, einen festgelegten Wochenplan oder eine API-basierte Quelle.
|
||||
|
||||
**Node-Typ:** Execute Sub-workflow
|
||||
**Name:** Elyra – Sendezeit abrufen
|
||||
|
||||
**Parameter:**
|
||||
- **Workflow:** `elyra_zeiten` *(bereitgestellt als JSON-Download)*
|
||||
- **Options → Wait for Completion:** `true`
|
||||
- **Options → Variables:**
|
||||
- `chatId`: `={{$json["chatId"]}}`
|
||||
- `text`: `={{$json["text"]}}`
|
||||
- `timestamp`: `={{$json["timestamp"]}}`
|
||||
|
||||
> [!NOTE]
|
||||
> Der Workflow `elyra_zeiten` wird separat als JSON bereitgestellt
|
||||
> und extrahiert z. B. aus einem Kalender die geplante Startzeit des Streams.
|
||||
> Die Ausgabe erfolgt idealerweise als String im Feld `sendezeit`.
|
||||
|
||||
👉 **Screenshot geeignet:** Execute Sub-workflow mit Workflow `elyra_zeiten` und gesetzten Übergabeparametern
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **→** weiter mit Node 20 – *Antwort generieren (Ollama mit Kontext)*
|
||||
|
||||
#### Node 17 – Execute Sub‑workflow (Chatbefehle aus StreamElements abrufen)
|
||||
|
||||
**Zweck:** Führt einen ausgelagerten Workflow aus, der die öffentlich zugängliche StreamElements‑Commands‑Seite abruft,
|
||||
alle verfügbaren Chatbefehle extrahiert und als JSON-String bereitstellt.
|
||||
|
||||
**Node-Typ:** Execute Sub-workflow
|
||||
**Name:** Elyra – Chatbefehle laden
|
||||
|
||||
**Parameter:**
|
||||
- **Workflow:** `elyra_chatcommands` *(bereitgestellt als JSON-Download)*
|
||||
- **Options → Wait for Completion:** `true`
|
||||
- **Options → Variables:**
|
||||
- `chatId`: `={{$json["chatId"]}}`
|
||||
- `text`: `={{$json["text"]}}`
|
||||
|
||||
> [!NOTE]
|
||||
> Dieser Sub‑Workflow ruft z. B. `https://streamelements.com/bratonien_tv/commands` auf,
|
||||
> analysiert das HTML und extrahiert die Befehle samt Beschreibung als JSON‑Objekt.
|
||||
> Das Ergebnis wird später an Ollama übergeben, damit Elyra kontextbezogen passende Befehle vorschlagen kann.
|
||||
>
|
||||
> Auch andere Anbieter wie **Nightbot**, **Moobot** oder **Botisimo** stellen vergleichbare Listen zur Verfügung –
|
||||
> im Rahmen dieses Tutorials verwenden wir jedoch ausschließlich **StreamElements** als zentrale Bot-Instanz.
|
||||
|
||||
👉 **Screenshot geeignet:** Execute Sub-workflow mit Workflow-Namen `elyra_chatcommands`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **→** weiter mit Node 20 – *Antwort generieren (Ollama mit Kontext)*
|
||||
|
||||
#### Node 18 – Execute Sub‑workflow (Adminrechte prüfen)
|
||||
|
||||
**Zweck:** Führt einen externen Workflow aus, der prüft, ob der aktuelle Absender (`chatId`) zu den autorisierten Admins gehört.
|
||||
Nur wenn das zutrifft, wird die Anfrage weiterverarbeitet – sonst erhält der Nutzer eine entsprechende Ablehnung.
|
||||
|
||||
**Node-Typ:** Execute Sub-workflow
|
||||
**Name:** Elyra – Adminrechte prüfen
|
||||
|
||||
**Parameter:**
|
||||
- **Workflow:** `elyra_admincheck` *(bereitgestellt als JSON-Download)*
|
||||
- **Options → Wait for Completion:** `true`
|
||||
- **Options → Variables:**
|
||||
- `chatId`: `={{$json["chatId"]}}`
|
||||
- `text`: `={{$json["text"]}}`
|
||||
|
||||
> [!NOTE]
|
||||
> Der Sub-Workflow `elyra_admincheck` vergleicht die `chatId` mit einer internen Liste autorisierter Nutzer
|
||||
> (z. B. als JSON-Datei, Umgebungsvariable oder Datenbankeintrag).
|
||||
> Ist der Nutzer nicht autorisiert, wird das Feld `admin_erlaubt` auf `false` gesetzt und die Anfrage verworfen.
|
||||
|
||||
👉 **Screenshot geeignet:** Execute Sub-workflow mit Workflow `elyra_admincheck` und Variable `chatId`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **→** weiter mit Node 20 – *Antwort generieren (Ollama mit Kontext)*
|
||||
|
||||
#### Node 19 – Set (Ablehnungsprompt für nicht erlaubtes Thema)
|
||||
|
||||
**Zweck:** Bereitet den Prompt für eine freundlich-bestimmte Ablehnungsantwort durch Ollama vor.
|
||||
Dies erfolgt, wenn das Thema vom Klassifizierungsmodell als `nicht zulässig` eingestuft wurde.
|
||||
|
||||
**Node-Typ:** Set
|
||||
**Name:** Elyra – Thema abgelehnt (Prompt vorbereiten)
|
||||
|
||||
**Felder hinzufügen:**
|
||||
- `chatId`: `={{$json["chatId"]}}`
|
||||
- `text`: `={{$json["text"]}}`
|
||||
- `ablehnungskontext`:
|
||||
```text
|
||||
Die folgende Anfrage konnte keinem zugelassenen Thema zugeordnet werden.
|
||||
Formuliere eine kurze, aber klare Antwort, die dem Nutzer freundlich mitteilt,
|
||||
dass diese Art von Anfrage aktuell nicht unterstützt wird.
|
||||
Die Antwort soll sachlich bleiben, aber höflich und professionell wirken.
|
||||
```
|
||||
|
||||
👉 **Screenshot geeignet:** Set-Node mit sichtbarem Feld `ablehnungskontext`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **→** weiter mit Node 20 – *Antwort generieren (Ollama mit Kontext)*
|
||||
|
||||
#### Node 20 – HTTP Request (Antwort generieren – Ollama)
|
||||
|
||||
**Zweck:** Sendet die Nutzeranfrage zusammen mit dem relevanten Kontext (z. B. Chatbefehle, Eventinfos, Ablehnungshinweis)
|
||||
an das lokale Sprachmodell **Mistral** über den Ollama-Endpunkt und erhält eine formulierte Antwort zurück.
|
||||
|
||||
**Node-Typ:** HTTP Request
|
||||
**Name:** Elyra – Antwort generieren (Ollama)
|
||||
|
||||
**Parameter:**
|
||||
- **Method:** `POST`
|
||||
- **URL:**
|
||||
```
|
||||
http://ollama.<deine-domain>.tld/api/generate
|
||||
```
|
||||
- **Headers:**
|
||||
- `Content-Type`: `application/json`
|
||||
- **Body → JSON:**
|
||||
```json
|
||||
{
|
||||
"model": "mistral",
|
||||
"stream": false,
|
||||
"prompt": "{{$json['ablehnungskontext'] || ''}}{{$json['kontext'] || ''}}\n\nNachricht:\n{{$json['text']}}"
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Der Prompt setzt sich aus verschiedenen Quellen zusammen – je nach Pfad:
|
||||
> – bei `chatbefehl`: Liste aller Chatkommandos (`kontext`)
|
||||
> – bei `eventinfo` oder `sendezeit`: Zusatzinfos aus Sub-Workflows
|
||||
> – bei `nicht erlaubt`: Ablehnungshinweis (`ablehnungskontext`)
|
||||
>
|
||||
> Der finale Prompt enthält immer den Originaltext des Nutzers.
|
||||
|
||||
> [!TIP]
|
||||
> Du kannst über die `prompt:`-Zusammensetzung bei Bedarf eigene Regeln einbauen,
|
||||
> z. B. Priorisierungen, stilistische Hinweise oder Sprachwechsel.
|
||||
|
||||
👉 **Screenshot geeignet:** HTTP-Request-Node mit Body-Feld `prompt`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- **→** weiter mit Node 21 – *Antwort an WAHA senden*
|
||||
|
||||
#### Node 21 – HTTP Request (Antwort über WAHA senden)
|
||||
|
||||
**Zweck:** Sendet die von Ollama generierte oder anderweitig erzeugte Antwort als WhatsApp-Nachricht über die WAHA‑API an den Nutzer.
|
||||
|
||||
**Node-Typ:** HTTP Request
|
||||
**Name:** Elyra – Antwort senden (WAHA)
|
||||
|
||||
**Parameter:**
|
||||
- **Method:** `POST`
|
||||
- **URL:**
|
||||
```
|
||||
https://waha.<deine-domain>.tld/api/sendText
|
||||
```
|
||||
- **Headers:**
|
||||
- `Content-Type`: `application/json`
|
||||
- *(optional)* `Authorization`: `Bearer <DEIN_WAHA_API_TOKEN>`
|
||||
- **Body → JSON:**
|
||||
```json
|
||||
{
|
||||
"chatId": "={{$json[\"chatId\"]}}",
|
||||
"text": "={{$json[\"antwort\"] || $json[\"text\"]}}"
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Das Feld `antwort` enthält entweder die direkte Antwort aus einem vorherigen Set‑Node
|
||||
> oder die von **Ollama** generierte Antwort aus dem HTTP-Request‑Node davor.
|
||||
> Falls `antwort` leer ist, wird ersatzweise `text` genutzt – z. B. für statische Fallbacks.
|
||||
|
||||
👉 **Screenshot geeignet:** HTTP Request mit gesetztem Body `chatId` + `text`
|
||||
|
||||
**Anschlusslogik:**
|
||||
- → **Ende des Workflows**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user