From 92a8363f5db2584b4215c35900e057bccb140606 Mon Sep 17 00:00:00 2001 From: TiffanyBrugger Date: Wed, 6 May 2026 15:33:47 +0000 Subject: [PATCH] Update Misty Coaching Dashboard und Analyse --- .gitignore | 5 ++ analyse.py | 33 +++---- dashboard.py | 51 ++++++++++- fuellwoerter.json | 12 +++ start_coaching.py | 85 +++++++++++++++--- templates/dashboard.html | 180 ++++++++++++++++++++++++++++++++------- 6 files changed, 304 insertions(+), 62 deletions(-) create mode 100644 .gitignore create mode 100644 fuellwoerter.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d59d5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +*.pyc +*.wav +session_daten.json +*.save diff --git a/analyse.py b/analyse.py index 29c010e..d5450fc 100644 --- a/analyse.py +++ b/analyse.py @@ -8,18 +8,21 @@ from datetime import datetime from config import MISTY_IP DATEN_DATEI = "session_daten.json" +FUELLWOERTER_DATEI = "fuellwoerter.json" -FUELLWOERTER = [ - r'\bäh\b', - r'\bähm\b', - r'\behm\b', - r'\bmhm\b', - r'\bhm\b', - r'\bhalt\b', - r'\balso\b', - r'\bsozusagen\b', - r'\birgendwie\b', -] +# Modell einmal beim Start laden +print("--- Lade Whisper-Modell ---") +_whisper_model = whisper.load_model("base") + +def lade_fuellwoerter(): + if os.path.exists(FUELLWOERTER_DATEI): + with open(FUELLWOERTER_DATEI, "r", encoding="utf-8") as f: + woerter = json.load(f) + return [rf'\b{w}\b' for w in woerter] + return [ + r'\bäh\b', r'\bähm\b', r'\behm\b', r'\bmhm\b', r'\bhm\b', + r'\bhalt\b', r'\balso\b', r'\bsozusagen\b', r'\birgendwie\b', + ] def setze_status(status): if os.path.exists(DATEN_DATEI): @@ -55,8 +58,7 @@ def speichere_session(text, anzahl, gefundene_woerter, tempo, feedback, gesicht) def analysiere(datei, echte_dauer=None): setze_status("analysierend") print("--- Whisper Analyse läuft ---") - model = whisper.load_model("base") - result = model.transcribe( + result = _whisper_model.transcribe( datei, language="German", initial_prompt="Dies ist eine Präsentation auf Deutsch. Äh, ähm, sozusagen, halt, also, irgendwie.", @@ -64,12 +66,13 @@ def analysiere(datei, echte_dauer=None): beam_size=5, best_of=5 ) - # Originaltext für Anzeige im Dashboard (mit Groß-/Kleinschreibung) text_original = result["text"].strip() - # Kleinbuchstabentext nur für Füllworterkennung text_lower = text_original.lower() print(f"Erkannter Text: {text_original}") + # Füllwörter aus Datei laden (bei jeder Analyse aktuell) + FUELLWOERTER = lade_fuellwoerter() + anzahl = 0 gefundene_woerter = {} for muster in FUELLWOERTER: diff --git a/dashboard.py b/dashboard.py index 6601f70..705c3dc 100644 --- a/dashboard.py +++ b/dashboard.py @@ -1,25 +1,68 @@ -from flask import Flask, render_template, jsonify +from flask import Flask, render_template, jsonify, request, redirect, url_for, Response import json import os +import time app = Flask(__name__, static_folder='static') DATEN_DATEI = "session_daten.json" +FUELLWOERTER_DATEI = "fuellwoerter.json" def lade_daten(): if os.path.exists(DATEN_DATEI): with open(DATEN_DATEI, "r", encoding="utf-8") as f: return json.load(f) - return {"status": "wartend", "sessions": []} + return {"status": "wartend", "sessions": [], "fehler": None} + +def lade_fuellwoerter(): + if os.path.exists(FUELLWOERTER_DATEI): + with open(FUELLWOERTER_DATEI, "r", encoding="utf-8") as f: + return json.load(f) + return ["äh", "ähm", "ehm", "mhm", "hm", "halt", "also", "sozusagen", "irgendwie"] + +def speichere_fuellwoerter(woerter): + with open(FUELLWOERTER_DATEI, "w", encoding="utf-8") as f: + json.dump(woerter, f, ensure_ascii=False, indent=2) @app.route("/") def index(): daten = lade_daten() - return render_template("dashboard.html", daten=daten) + fuellwoerter = lade_fuellwoerter() + return render_template("dashboard.html", daten=daten, fuellwoerter=fuellwoerter) @app.route("/api/daten") def api_daten(): return jsonify(lade_daten()) +@app.route("/stream") +def stream(): + def event_stream(): + letzter_stand = None + while True: + daten = lade_daten() + aktueller_stand = json.dumps(daten, ensure_ascii=False) + if aktueller_stand != letzter_stand: + letzter_stand = aktueller_stand + yield f"data: {aktueller_stand}\n\n" + time.sleep(1) + return Response(event_stream(), mimetype="text/event-stream") + +@app.route("/fuellwort/hinzufuegen", methods=["POST"]) +def fuellwort_hinzufuegen(): + woerter = lade_fuellwoerter() + neues_wort = request.form.get("wort", "").strip().lower() + if neues_wort and neues_wort not in woerter: + woerter.append(neues_wort) + speichere_fuellwoerter(woerter) + return redirect(url_for("index")) + +@app.route("/fuellwort/entfernen/") +def fuellwort_entfernen(wort): + woerter = lade_fuellwoerter() + if wort in woerter: + woerter.remove(wort) + speichere_fuellwoerter(woerter) + return redirect(url_for("index")) + if __name__ == "__main__": - app.run(host="0.0.0.0", port=5000, debug=True) + app.run(host="0.0.0.0", port=5000, debug=True, threaded=True) diff --git a/fuellwoerter.json b/fuellwoerter.json new file mode 100644 index 0000000..0248771 --- /dev/null +++ b/fuellwoerter.json @@ -0,0 +1,12 @@ +[ + "äh", + "ähm", + "ehm", + "mhm", + "hm", + "also", + "sozusagen", + "irgendwie", + "test", + "nochmal test" +] \ No newline at end of file diff --git a/start_coaching.py b/start_coaching.py index d0037f9..28f0e86 100644 --- a/start_coaching.py +++ b/start_coaching.py @@ -15,13 +15,35 @@ aufnahme_laeuft = False aufnahme_start = 0 def setze_neutral(): - requests.post(f"http://{MISTY_IP}/api/images/display", json={"FileName": "e_DefaultContent.jpg"}) + try: + requests.post(f"http://{MISTY_IP}/api/images/display", + json={"FileName": "e_DefaultContent.jpg"}, timeout=5) + except Exception: + pass def reset_session_daten(): with open(DATEN_DATEI, "w", encoding="utf-8") as f: - json.dump({"status": "wartend", "sessions": []}, f, ensure_ascii=False, indent=2) + json.dump({"status": "wartend", "sessions": [], "fehler": None}, f, ensure_ascii=False, indent=2) print("--- Session-Daten zurückgesetzt ---") +def pruefe_misty_verbindung(): + try: + r = requests.get(f"http://{MISTY_IP}/api/device", timeout=5) + return r.status_code == 200 + except Exception: + return False + +def setze_fehler(nachricht): + if os.path.exists(DATEN_DATEI): + with open(DATEN_DATEI, "r", encoding="utf-8") as f: + daten = json.load(f) + else: + daten = {"status": "fehler", "sessions": [], "fehler": nachricht} + daten["status"] = "fehler" + daten["fehler"] = nachricht + with open(DATEN_DATEI, "w", encoding="utf-8") as f: + json.dump(daten, f, ensure_ascii=False, indent=2) + def coaching_prozess(): global laeuft_gerade, aufnahme_laeuft, aufnahme_start laeuft_gerade = True @@ -31,14 +53,26 @@ def coaching_prozess(): setze_status("aufnehmend") print("--- Fuß gedrückt! Starte Coaching ---") - requests.post(f"http://{MISTY_IP}/api/tts/speak", json={ - "text": "Ich höre dir zu. Drücke meinen Fuß nochmal wenn du fertig bist.", - "voice": "de-de-x-deb-local" - }) + try: + requests.post(f"http://{MISTY_IP}/api/tts/speak", json={ + "text": "Ich höre dir zu. Drücke meinen Fuß nochmal wenn du fertig bist.", + "voice": "de-de-x-deb-local" + }, timeout=5) + except Exception as e: + print(f"❌ Fehler bei TTS: {e}") + time.sleep(4) print("--- Aufnahme läuft ---") - requests.post(f"http://{MISTY_IP}/api/audio/record/start", json={"FileName": DATEI_NAME}) + try: + requests.post(f"http://{MISTY_IP}/api/audio/record/start", + json={"FileName": DATEI_NAME}, timeout=5) + except Exception as e: + print(f"❌ Fehler beim Starten der Aufnahme: {e}") + setze_fehler(f"Aufnahme konnte nicht gestartet werden: {e}") + laeuft_gerade = False + return + aufnahme_start = time.time() while aufnahme_laeuft: @@ -46,16 +80,29 @@ def coaching_prozess(): echte_dauer = time.time() - aufnahme_start print(f"--- Aufnahme gestoppt ({round(echte_dauer)} Sekunden) ---") - requests.post(f"http://{MISTY_IP}/api/audio/record/stop") + + try: + requests.post(f"http://{MISTY_IP}/api/audio/record/stop", timeout=5) + except Exception as e: + print(f"❌ Fehler beim Stoppen der Aufnahme: {e}") + time.sleep(2) print("--- Übertragung der Audio-Datei ---") - r = requests.get(f"http://{MISTY_IP}/api/audio?FileName={DATEI_NAME}") - if r.status_code == 200: - with open(DATEI_NAME, 'wb') as f: - f.write(r.content) - else: - print("Download-Fehler!") + try: + r = requests.get(f"http://{MISTY_IP}/api/audio?FileName={DATEI_NAME}", timeout=20) + if r.status_code == 200: + with open(DATEI_NAME, 'wb') as f: + f.write(r.content) + else: + print("Download-Fehler!") + setze_fehler("Audiodatei konnte nicht heruntergeladen werden.") + setze_status("wartend") + laeuft_gerade = False + return + except Exception as e: + print(f"❌ Fehler beim Download: {e}") + setze_fehler(f"Verbindung zu Misty verloren: {e}") setze_status("wartend") laeuft_gerade = False return @@ -94,12 +141,22 @@ def on_open(ws): def on_error(ws, error): print(f"❌ WebSocket Fehler: {error}") + setze_fehler(f"WebSocket Fehler: {error}") def on_close(ws, close_status_code, close_msg): print(f"❌ Verbindung getrennt: {close_status_code} - {close_msg}") setze_neutral() setze_status("wartend") +# Misty Verbindung prüfen vor dem Start +print("--- Prüfe Verbindung zu Misty ---") +if not pruefe_misty_verbindung(): + print(f"❌ Misty nicht erreichbar unter {MISTY_IP}") + print("Bitte IP in config.py prüfen und Misty einschalten!") + setze_fehler(f"Misty nicht erreichbar unter {MISTY_IP}. Bitte IP prüfen und Misty einschalten.") +else: + print(f"✅ Misty erreichbar unter {MISTY_IP}") + ws = websocket.WebSocketApp( f"ws://{MISTY_IP}/pubsub", on_open=on_open, diff --git a/templates/dashboard.html b/templates/dashboard.html index 8891e83..4b587a8 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -3,7 +3,6 @@ - Misty Rhetorik-Coach @@ -209,21 +252,55 @@ header {
🤖
Misty Rhetorik-Coach
-
PH Weingarten · Modul M2 Entwicklung Interaktiver Medien
+
PH Weingarten · Modul M2 · WS 2025/26
-
-
- {% if daten.status == "wartend" %}Wartet auf Fußdruck - {% elif daten.status == "aufnehmend" %}Aufnahme läuft... - {% elif daten.status == "analysierend" %}Analyse läuft... - {% endif %} +
+
+ + {% if daten.status == "wartend" %}Wartet auf Fußdruck + {% elif daten.status == "aufnehmend" %}Aufnahme läuft... + {% elif daten.status == "analysierend" %}Analyse läuft... + {% elif daten.status == "fehler" %}Fehler aufgetreten + {% endif %} +
+ +{% if daten.status == "fehler" and daten.fehler %} +
+
⚠️
+
+
Verbindungsfehler
+
{{ daten.fehler }}
+
+
+{% endif %} + + +
+
Füllwörter verwalten
+
+ {% for wort in fuellwoerter %} + + {{ wort }} + × + + {% endfor %} +
+
+ + +
+
+ + +
{% if daten.sessions %} {% set letzte = daten.sessions[-1] %} -
· · · · ·
· · · · ·
· · · · ·
@@ -241,7 +318,6 @@ header { {% endif %} {% endif %}
-
Sprechtempo
{{ letzte.tempo }}
@@ -273,7 +349,6 @@ header {
zu schnell
{% endif %}
-
Mistys Reaktion
@@ -284,7 +359,6 @@ header {
-
Erkannter Text
@@ -297,14 +371,12 @@ header {
{% endif %}
-
Feedback von Misty
- {% if daten.sessions|length > 1 %}
Sessionverlauf
@@ -330,19 +402,69 @@ header { {% endfor %}
{% endif %} - {% else %}
-
- 🤖 Willkommen beim Misty Rhetorik-Coach!

- Hier werden die Ergebnisse jeder Präsentation – Füllwörter, Sprechtempo und Mistys Feedback gezeigt.

- Sobald der erste Durchgang abgeschlossen ist, erscheinen die Ergebnisse automatisch.

- Im Sessionverlauf ist zu sehen, wie sich die Leistung über mehrere Durchgänge hinweg verbessert.

- Drücke Mistys Fuß um die Aufnahme zu starten! -
+ 🤖 Willkommen beim Misty Rhetorik-Coach!

+ Hier werden die Ergebnisse jeder Präsentation – Füllwörter, Sprechtempo und Mistys Feedback gezeigt.

+ Sobald der erste Durchgang abgeschlossen ist, erscheinen die Ergebnisse automatisch.

+ Im Sessionverlauf ist zu sehen, wie sich die Leistung über mehrere Durchgänge hinweg verbessert.

+ 👣 Drücke Mistys Fuß um die Aufnahme zu starten! +
{% endif %} +
+ +