Spaces:
Running
Running
72832f4ef2259d5c0fb3513e6af528220387435e
#1
by sky-meilin - opened
This view is limited to 50 files because it contains too many changes. See the raw diff here.
- Docker +0 -11
- README.md +6 -63
- SKYOS_BLUEPRINT_PHASE_1.md +18 -16
- agency_core.py +0 -176
- app.py +23 -80
- audio_core.py +0 -16
- code_runner.py +16 -36
- core/chunker.py +9 -8
- core/cleaner.py +8 -10
- core/deepseek.py +2 -2
- core/embeddings.py +2 -11
- core/hermes.py +2 -2
- core/joyai.py +2 -2
- core/loader.py +24 -22
- core/orchestrator.py +68 -31
- core/router.py +1 -15
- crm_core.py +7 -39
- extractor.py +14 -26
- fanvue_guide.py +3 -18
- mcp_server.py +10 -40
- modules/adapters/base_adapter.py +0 -33
- modules/adapters/huggingface_adapter.py +0 -154
- modules/adapters/init.py +0 -24
- modules/adapters/sheets_adapter.py +0 -175
- modules/avatar_streamer.py +24 -23
- modules/creator_os.py +4 -15
- modules/crypto_engine.py +29 -14
- modules/dashboard/__init__.py +0 -112
- modules/dashboard/chat_panel.py +0 -41
- modules/dashboard/config.py +0 -97
- modules/dashboard/dashboard.py +0 -74
- modules/dashboard/event_handler.py +0 -184
- modules/dashboard/header.py +0 -113
- modules/dashboard/icons.py +0 -114
- modules/dashboard/navigation_panel.py +0 -53
- modules/dashboard/status_panel.py +0 -78
- modules/dashboard/theme.py +0 -66
- modules/dashboard/utils.py +0 -91
- modules/fanvue_guide.py +4 -4
- modules/google/__init__.py +0 -42
- modules/google/auth.py +0 -126
- modules/google/calendar_gateway.py +0 -166
- modules/google/config.py +0 -121
- modules/google/discovery.py +0 -110
- modules/google/docs_gateway.py +0 -154
- modules/google/drive_gateway.py +0 -57
- modules/google/gmail_gateway.py +0 -162
- modules/google/oauth.py +0 -117
- modules/google/utils.py +0 -130
- modules/remote_control.py +30 -17
Docker
DELETED
|
@@ -1,11 +0,0 @@
|
|
| 1 |
-
docker run -it -p 7860:7860 --platform=linux/amd64 \
|
| 2 |
-
-e HF_READ_TOKEN="YOUR_VALUE_HERE" \
|
| 3 |
-
-e HF_TOKEN="YOUR_VALUE_HERE" \
|
| 4 |
-
-e WP_APP_PASSWORD="YOUR_VALUE_HERE" \
|
| 5 |
-
-e DIGITAL_PRODUCT_CSV_URL="https://docs.google.com/spreadsheets/d/1ARXP9Oh5dp0uS0N09ymGJMDsLaAbzcWihb-jaaUwIuI/export?format=csv&gid=2026061006" \
|
| 6 |
-
-e DEEPSEEK_MEMORY_CSV_URL="https://docs.google.com/spreadsheets/d/14XwhyeMxZJbEKUcWfa4_D4fajnzMTgoblVz4lTXFRpE/export?format=csv&gid=419179114" \
|
| 7 |
-
-e DEFAULT_LANGUAGE="de" \
|
| 8 |
-
-e APP_ENV="production" \
|
| 9 |
-
-e WORDPRESS_AUTH_MODE="mcp" \
|
| 10 |
-
-e WORDPRESS_MCP_URL="https://public-api.wordpress.com/wpcom/v2/mcp/v1" \
|
| 11 |
-
registry.hf.space/sky-meilin-skyosfullcore:cpu-0040161-9lwc09te python app.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,70 +1,13 @@
|
|
| 1 |
---
|
| 2 |
title: SkyOSFullCore
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 6.
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
| 13 |
-
**Version:** `skyos_v3_core_2026_06_28`
|
| 14 |
-
**Plattform-Kompatibilität:** Optimiert für Android 15 (Redmi 13) & Hugging Face Spaces Runtime
|
| 15 |
-
|
| 16 |
-
---
|
| 17 |
-
|
| 18 |
-
## 🏢 System-Architektur
|
| 19 |
-
|
| 20 |
-
SkyOS ist ein modular aufgebautes Betriebssystem zur Automatisierung von Agentur-Prozessen, Lead-Verarbeitung, Trend-Vorhersagen und Content-Infrastrukturen. Die Architektur trennt strikt die Benutzeroberfläche von der Verarbeitungslogik und den Daten-Gateways.
|
| 21 |
-
|
| 22 |
-
### 📁 Modul-Übersicht
|
| 23 |
-
|
| 24 |
-
* **`app.py` (v3_audio):** Das zentrale Steuerpult. Eine schlanke Gradio-Oberfläche, die vollständig dynamisch geladen wird und sowohl Textbefehle als auch native Audio-Direktaufnahmen verarbeitet.
|
| 25 |
-
* **`agency_core.py`:** Die Single Source of Truth für das Agentur-Grid. Verwaltet 15 Fachabteilungen, aufgeteilt in 5 strategische Cluster. Jede Abteilung entspricht einer isolierten Location (Tabellenplatz) im Google Sheets Backend.
|
| 26 |
-
* **`audio_core.py`:** Das Sprachverarbeitungs-Modul. Verarbeitet `.wav`/`.mp3`-Streams aus dem UI-Mikrofon, transkribiert diese über Whisper und speist die Text-Intents direkt in den Core-Router ein.
|
| 27 |
-
* **`router.py`:** Nimmt universale Befehle entgegen, validiert System-Status und verteilt die Aufgaben an die Sub-Module. Enthält eine abgesicherte Fehlerbehandlung (`safe_status_query`), um JSON-Parse-Fehler im Frontend zu verhindern.
|
| 28 |
-
* **`extractor.py` / `crm_core.py`:** Analysiert eingehende Nachrichten auf Intents, extrahiert Kontakt- und Prioritätsdaten für die Lead-Pipeline.
|
| 29 |
-
* **`trend_predictor.py`:** Mathematische Engine zur Berechnung des *Virality Index* basierend auf Share-Rate, Interaktionstiefe, Hook-Stärke und Marktsättigung.
|
| 30 |
-
* **`worklog.py`:** Protokolliert jede System-Aktion im JSONL-Stream zur lückenlosen Diagnose.
|
| 31 |
-
* **`sheets_gateway.py`:** Die Schnittstelle zur persistenten Live-Datenbank (`best_practice`), die Änderungen direkt in das korrespondierende Tabellenblatt einträgt.
|
| 32 |
-
|
| 33 |
-
---
|
| 34 |
-
|
| 35 |
-
## 🗂️ Multidimensionales Agentur-Grid
|
| 36 |
-
|
| 37 |
-
Die Zuweisung und Speicherung erfolgt automatisiert über die in `agency_core.py` definierten Cluster und deren Tabellenplätze:
|
| 38 |
-
|
| 39 |
-
### 1. Management & Operations
|
| 40 |
-
* **Executive Leadership (CEO, Managing Director):** `executive_leadership`
|
| 41 |
-
* **Operations / Administration (HR, Finance, Legal):** `operations_admin`
|
| 42 |
-
* **Project Management:** `project_management`
|
| 43 |
-
|
| 44 |
-
### 2. Client & Strategy
|
| 45 |
-
* **Account Management / Client Services:** `account_management`
|
| 46 |
-
* **Strategy & Planning:** `strategy_planning`
|
| 47 |
-
|
| 48 |
-
### 3. Creation & Content
|
| 49 |
-
* **Creative Department (Design, Copywriting):** `creative_department`
|
| 50 |
-
* **Content Marketing:** `content_marketing`
|
| 51 |
-
* **Social Media Management:** `social_media`
|
| 52 |
-
* **Community Management:** `community_management`
|
| 53 |
-
|
| 54 |
-
### 4. Technical & Performance Marketing
|
| 55 |
-
* **SEO / Technical SEO:** `seo_technical`
|
| 56 |
-
* **Paid Media / PPC:** `paid_media`
|
| 57 |
-
* **Media Buying:** `media_buying`
|
| 58 |
-
* **Email Marketing:** `email_marketing`
|
| 59 |
-
|
| 60 |
-
### 5. Tech & Data
|
| 61 |
-
* **Development / Engineering / Web Development:** `development_engineering`
|
| 62 |
-
* **Analytics & Data / Business Intelligence:** `analytics_bi`
|
| 63 |
-
|
| 64 |
-
---
|
| 65 |
-
|
| 66 |
-
## 🚀 Installation & Start
|
| 67 |
-
|
| 68 |
-
### Voraussetzungen
|
| 69 |
-
```bash
|
| 70 |
-
pip install gradio openai-whisper
|
|
|
|
| 1 |
---
|
| 2 |
title: SkyOSFullCore
|
| 3 |
+
emoji: 👁
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 6.14.0
|
| 8 |
+
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
---
|
| 12 |
|
| 13 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SKYOS_BLUEPRINT_PHASE_1.md
CHANGED
|
@@ -1,22 +1,24 @@
|
|
| 1 |
-
# Sky - Die Lösung: Phase 1 - Ökosystem-Setup Blueprint
|
| 2 |
|
| 3 |
## 1. Startpunkt für die Verbindungen festlegen (Der Gateway)
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
|
| 9 |
## 2. API bereitstellen (Die Proprietäre Schnittstelle)
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
| 14 |
|
| 15 |
## 3. Daten einspeisen (Context Injection)
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
1.
|
| 20 |
-
2.
|
| 21 |
-
3.
|
| 22 |
-
|
|
|
|
| 1 |
+
Kann meine Nummer# Sky - Die Lösung: Phase 1 - Ökosystem-Setup Blueprint
|
| 2 |
|
| 3 |
## 1. Startpunkt für die Verbindungen festlegen (Der Gateway)
|
| 4 |
+
- **Komponente:** Layer 2: Firebase Functions (Backend Logic).
|
| 5 |
+
- **Rolle:** Unüberwindbarer Gateway und zentrale Schleuse.
|
| 6 |
+
- **Funktion:** Alle Anfragen vom Hugging Face Space (Layer 1) werden an einen verschlüsselten Firebase Endpoint gesendet.
|
| 7 |
+
- **Aktion:** Firebase Function als Haupt-API-Endpoint definieren.
|
| 8 |
|
| 9 |
## 2. API bereitstellen (Die Proprietäre Schnittstelle)
|
| 10 |
+
- **Public API (Layer 1 zu Layer 2):**
|
| 11 |
+
- Gradio App konfiguriert, um Benutzeranfrage via **HTTP POST** an den Firebase Function Endpoint zu senden.
|
| 12 |
+
- **Private API (Layer 2 zu Layer 3):**
|
| 13 |
+
- Firebase führt **Validierungen** durch (Rate Limiting, Usage Tracking).
|
| 14 |
+
- Ruft die **Layer 3: Core Logic** auf (Proprietary Prompt Engineering).
|
| 15 |
+
- **IP-Schutz:** Sicherstellen, dass Layer 3 nur **intern** von Firebase aufgerufen werden kann und die eigentliche Logik niemals exponiert wird.
|
| 16 |
|
| 17 |
## 3. Daten einspeisen (Context Injection)
|
| 18 |
+
- **Datenquelle:** 200 Assets des generischen KI-Influencer-Assets.
|
| 19 |
+
- **Format:** Strukturierte Metadaten und Kontext-Vektoren.
|
| 20 |
+
- **Integration:** Die Vektoren bilden den **"Context Injection"**-Teil des internen Workflows:
|
| 21 |
+
1. Base Prompt Template
|
| 22 |
+
2. Context Injection (Deine 200 Assets)
|
| 23 |
+
3. Style Transfer Layer
|
| 24 |
+
- **Ziel:** Optimales Informieren des Custom Model Fine-tunings für den unfairen Vorteil.
|
agency_core.py
DELETED
|
@@ -1,176 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
agency_core.py
|
| 3 |
-
SkyOS Agency Architecture Core
|
| 4 |
-
Version: skyos_agency_core_v1_2026_06_28
|
| 5 |
-
Verwaltet die Abteilungsstrukturen, Rollen-Mappings und Berechtigungen separat von app.py.
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
from __future__ import annotations
|
| 9 |
-
from typing import Any, Dict, List, Optional
|
| 10 |
-
|
| 11 |
-
AGENCY_CORE_VERSION = "skyos_agency_core_v1_2026_06_28"
|
| 12 |
-
|
| 13 |
-
# Definition der exakten Abteilungsstruktur (Single Source of Truth)
|
| 14 |
-
DEPARTMENTS: Dict[str, Dict[str, Any]] = {
|
| 15 |
-
"executive_leadership": {
|
| 16 |
-
"name": "Executive Leadership (CEO, Managing Director)",
|
| 17 |
-
"cluster": "Management & Operations",
|
| 18 |
-
"description": "Gesamtstrategie, Vision, Skalierung und finale Freigaben.",
|
| 19 |
-
"allowed_routers": ["router.py", "router_publish.py"]
|
| 20 |
-
},
|
| 21 |
-
"account_management": {
|
| 22 |
-
"name": "Account Management / Client Services",
|
| 23 |
-
"cluster": "Client & Strategy",
|
| 24 |
-
"description": "Kundenbetreuung, Onboarding, Upselling und primäre Schnittstelle für Leads.",
|
| 25 |
-
"allowed_routers": ["router.py"]
|
| 26 |
-
},
|
| 27 |
-
"strategy_planning": {
|
| 28 |
-
"name": "Strategy & Planning",
|
| 29 |
-
"cluster": "Client & Strategy",
|
| 30 |
-
"description": "Konzeption, Marktanalysen, Zielgruppendefinition und Kampagnenausrichtung.",
|
| 31 |
-
"allowed_routers": ["router.py"]
|
| 32 |
-
},
|
| 33 |
-
"creative_department": {
|
| 34 |
-
"name": "Creative Department (Design, Copywriting)",
|
| 35 |
-
"cluster": "Creation & Content",
|
| 36 |
-
"description": "Visuelle Assets, Werbetexte, Branding, Layouts und Skripte.",
|
| 37 |
-
"allowed_routers": ["router.py"]
|
| 38 |
-
},
|
| 39 |
-
"content_marketing": {
|
| 40 |
-
"name": "Content Marketing",
|
| 41 |
-
"cluster": "Creation & Content",
|
| 42 |
-
"description": "Langfristige Content-Strategien, Blog-Artikel, Whitepaper und SEO-Inhalte.",
|
| 43 |
-
"allowed_routers": ["router.py"]
|
| 44 |
-
},
|
| 45 |
-
"seo_technical": {
|
| 46 |
-
"name": "SEO / Technical SEO",
|
| 47 |
-
"cluster": "Technical & Performance Marketing",
|
| 48 |
-
"description": "Organische Reichweite, OnPage-/OffPage-Optimierung, Core Web Vitals.",
|
| 49 |
-
"allowed_routers": ["router.py"]
|
| 50 |
-
},
|
| 51 |
-
"paid_media": {
|
| 52 |
-
"name": "Paid Media / PPC",
|
| 53 |
-
"cluster": "Technical & Performance Marketing",
|
| 54 |
-
"description": "Bezahlte Werbekampagnen auf Google Ads, Meta Ads, TikTok Ads.",
|
| 55 |
-
"allowed_routers": ["router.py"]
|
| 56 |
-
},
|
| 57 |
-
"social_media": {
|
| 58 |
-
"name": "Social Media Management",
|
| 59 |
-
"cluster": "Creation & Content",
|
| 60 |
-
"description": "Kanalkuration, Redaktionspläne, Posting-Taktung (TikTok, Instagram).",
|
| 61 |
-
"allowed_routers": ["router.py", "router_publish.py"]
|
| 62 |
-
},
|
| 63 |
-
"email_marketing": {
|
| 64 |
-
"name": "Email Marketing",
|
| 65 |
-
"cluster": "Technical & Performance Marketing",
|
| 66 |
-
"description": "Newsletter, automatisierte E-Mail-Funnel, Lead-Nurturing.",
|
| 67 |
-
"allowed_routers": ["router.py"]
|
| 68 |
-
},
|
| 69 |
-
"analytics_bi": {
|
| 70 |
-
"name": "Analytics & Data / Business Intelligence",
|
| 71 |
-
"cluster": "Tech & Data",
|
| 72 |
-
"description": "Tracking-Infrastruktur, Live-Dashboards, ROI-Berechnung.",
|
| 73 |
-
"allowed_routers": ["router.py"]
|
| 74 |
-
},
|
| 75 |
-
"development_engineering": {
|
| 76 |
-
"name": "Development / Engineering / Web Development",
|
| 77 |
-
"cluster": "Tech & Data",
|
| 78 |
-
"description": "WordPress-Setups, API-Anbindungen, SkyOS Core-Entwicklung.",
|
| 79 |
-
"allowed_routers": ["router.py", "router_publish.py"]
|
| 80 |
-
},
|
| 81 |
-
"operations_admin": {
|
| 82 |
-
"name": "Operations / Administration (HR, Finance, Legal)",
|
| 83 |
-
"cluster": "Management & Operations",
|
| 84 |
-
"description": "Verträge, Rechnungsstellung, HR-Prozesse, interne Compliance.",
|
| 85 |
-
"allowed_routers": ["router.py"]
|
| 86 |
-
},
|
| 87 |
-
"media_buying": {
|
| 88 |
-
"name": "Media Buying",
|
| 89 |
-
"cluster": "Technical & Performance Marketing",
|
| 90 |
-
"description": "Einkauf von Werbeplätzen, Budget-Verwaltung, Verhandlungen.",
|
| 91 |
-
"allowed_routers": ["router.py"]
|
| 92 |
-
},
|
| 93 |
-
"community_management": {
|
| 94 |
-
"name": "Community Management",
|
| 95 |
-
"cluster": "Creation & Content",
|
| 96 |
-
"description": "Interaktion mit Followern, Kommentar-Moderation, DM-Automations.",
|
| 97 |
-
"allowed_routers": ["router.py"]
|
| 98 |
-
},
|
| 99 |
-
"project_management": {
|
| 100 |
-
"name": "Project Management",
|
| 101 |
-
"cluster": "Management & Operations",
|
| 102 |
-
"description": "Terminplanung, Ressourcenverteilung, Task-Monitoring.",
|
| 103 |
-
"allowed_routers": ["router.py"]
|
| 104 |
-
}
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
def get_agency_status() -> Dict[str, Any]:
|
| 109 |
-
"""Gibt den Status und die Version des Agency-Moduls zurück."""
|
| 110 |
-
return {
|
| 111 |
-
"module": "agency_core",
|
| 112 |
-
"version": AGENCY_CORE_VERSION,
|
| 113 |
-
"status": "active",
|
| 114 |
-
"department_count": len(DEPARTMENTS)
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
def get_department_details(dept_key: str) -> Optional[Dict[str, Any]]:
|
| 119 |
-
"""Holt die Metadaten einer spezifischen Abteilung über ihren System-Key."""
|
| 120 |
-
return DEPARTMENTS.get(str(dept_key or "").lower().strip(), None)
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
def get_departments_by_cluster(cluster_name: str) -> List[Dict[str, Any]]:
|
| 124 |
-
"""Filtert alle Abteilungen, die zu einem bestimmten Cluster gehören."""
|
| 125 |
-
target_cluster = str(cluster_name or "").lower().strip()
|
| 126 |
-
result = []
|
| 127 |
-
for _, data in DEPARTMENTS.items():
|
| 128 |
-
if target_cluster in data["cluster"].lower():
|
| 129 |
-
result.append(data)
|
| 130 |
-
return result
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
def route_task_to_department(task_description: str) -> Dict[str, Any]:
|
| 134 |
-
"""
|
| 135 |
-
Heuristische Intent-Erkennung, um eine Aufgabe automatisch
|
| 136 |
-
der passenden Abteilung zuzuweisen.
|
| 137 |
-
"""
|
| 138 |
-
text = str(task_description or "").lower().strip()
|
| 139 |
-
|
| 140 |
-
if any(w in text for w in ["code", "bug", "api", "server", "datenbank", "plugin", "wordpress"]):
|
| 141 |
-
dept = "development_engineering"
|
| 142 |
-
elif any(w in text for w in ["rechnung", "vertrag", "geld", "finance", "legal", "mitarbeiter"]):
|
| 143 |
-
dept = "operations_admin"
|
| 144 |
-
elif any(w in text for w in ["seo", "keywords", "ranking", "search", "vitals"]):
|
| 145 |
-
dept = "seo_technical"
|
| 146 |
-
elif any(w in text for w in ["ads", "ppc", "google ads", "meta ads", "kampagne"]):
|
| 147 |
-
dept = "paid_media"
|
| 148 |
-
elif any(w in text for w in ["newsletter", "mail", "funnel", "automation"]):
|
| 149 |
-
dept = "email_marketing"
|
| 150 |
-
elif any(w in text for w in ["tracking", "dashboard", "analytics", "daten", "report"]):
|
| 151 |
-
dept = "analytics_bi"
|
| 152 |
-
elif any(w in text for w in ["design", "logo", "copy", "texten", "werbetext", "grafik"]):
|
| 153 |
-
dept = "creative_department"
|
| 154 |
-
elif any(w in text for w in ["posting", "instagram", "tiktok", "reel", "shorts", "planen"]):
|
| 155 |
-
dept = "social_media"
|
| 156 |
-
elif any(w in text for w in ["kommentar", "dm", "nachricht", "interaktion", "community"]):
|
| 157 |
-
dept = "community_management"
|
| 158 |
-
elif any(w in text for w in ["termin", "deadline", "zeitplan", "board", "task"]):
|
| 159 |
-
dept = "project_management"
|
| 160 |
-
elif any(w in text for w in ["kunde", "onboarding", "absprache", "call"]):
|
| 161 |
-
dept = "account_management"
|
| 162 |
-
elif any(w in text for w in ["strategie", "analyse", "ausrichtung", "konzept"]):
|
| 163 |
-
dept = "strategy_planning"
|
| 164 |
-
else:
|
| 165 |
-
dept = "project_management" # Default-Fallback
|
| 166 |
-
|
| 167 |
-
return {
|
| 168 |
-
"status": "assigned",
|
| 169 |
-
"assigned_department_key": dept,
|
| 170 |
-
"department_name": DEPARTMENTS[dept]["name"],
|
| 171 |
-
"cluster": DEPARTMENTS[dept]["cluster"]
|
| 172 |
-
}
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
if __name__ == "__main__":
|
| 176 |
-
print(get_agency_status())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
CHANGED
|
@@ -1,89 +1,32 @@
|
|
| 1 |
-
"""
|
| 2 |
-
app.py
|
| 3 |
-
SkyOS Dashboard Control Center – Simple Studio Edition
|
| 4 |
-
Komplett fokussiert auf Streaming, Chat & Direktnavigation
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
import gradio as gr
|
| 8 |
-
from
|
| 9 |
-
import
|
| 10 |
-
from
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
)
|
| 14 |
-
|
| 15 |
-
try:
|
| 16 |
-
from modules.avatar_streamer import AvatarStreamer
|
| 17 |
-
streamer = AvatarStreamer()
|
| 18 |
-
streamer.set_avatar(AVATAR_PERSONA)
|
| 19 |
-
AVATAR_READY = True
|
| 20 |
-
except Exception as e:
|
| 21 |
-
AVATAR_READY = False
|
| 22 |
-
|
| 23 |
-
tz_berlin = zoneinfo.ZoneInfo("Europe/Berlin")
|
| 24 |
|
| 25 |
-
|
| 26 |
-
if not AVATAR_READY:
|
| 27 |
-
return "Eingabe registriert. Render-Pipeline wird vorbereitet.", None
|
| 28 |
-
|
| 29 |
-
# Nutzt Text-Eingabe, fällt auf Audio-Verarbeitung zurück, falls Text leer ist
|
| 30 |
-
input_source = text if text else "Sprachbefehl verarbeitet."
|
| 31 |
-
manifest = build_studio_manifest(input_source, lighting, material)
|
| 32 |
-
|
| 33 |
-
streamer.start_stream()
|
| 34 |
-
streamer.send_message(f"Execute: {manifest}")
|
| 35 |
-
|
| 36 |
-
response = "Ich starte das Live-Streaming mit den gewünschten visuellen Anpassungen."
|
| 37 |
-
return [(input_source, response)], None
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
with gr.Row():
|
| 43 |
-
gr.Markdown(f"# 🌌 {SYSTEM_NAME} – {AVATAR_PERSONA} Studio")
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
label="Studio Stream (9:16)",
|
| 51 |
-
interactive=False
|
| 52 |
-
)
|
| 53 |
-
|
| 54 |
-
# Rechte Spalte: Chat & Steuerung
|
| 55 |
-
with gr.Column(scale=2):
|
| 56 |
-
chat_feed = gr.Chatbot(label="Chat-Interface (Aktiv wenn kein Stream)")
|
| 57 |
-
|
| 58 |
-
with gr.Row():
|
| 59 |
-
msg_input = gr.Textbox(placeholder="Nachricht oder Skript eingeben...", label="Texteingabe", scale=3)
|
| 60 |
-
audio_input = gr.Audio(sources=["microphone"], type="filepath", label="Spracheingabe", scale=2)
|
| 61 |
-
|
| 62 |
-
with gr.Row():
|
| 63 |
-
lighting_profile = gr.Dropdown(choices=list(LIGHTING_PROFILES.keys()), value="Golden Hour", label="☀️ Licht")
|
| 64 |
-
material_profile = gr.Dropdown(choices=list(MATERIAL_PROFILES.keys()), value="Lack & Leder (High-Gloss)", label="🧥 Outfit")
|
| 65 |
-
|
| 66 |
-
submit_btn = gr.Button("Senden & Streamen", variant="primary")
|
| 67 |
|
| 68 |
-
|
|
|
|
|
|
|
| 69 |
|
| 70 |
-
|
| 71 |
-
gr.Markdown("### 🏢 Bereiche öffnen")
|
| 72 |
-
with gr.Row():
|
| 73 |
-
# Die Buttons nutzen target="_blank" im HTML-Kontext, um isolierte Tabs zu öffnen
|
| 74 |
-
gr.HTML(f'<a href="/management" target="_blank"><button style="width:100%; padding:15px; background-color:#f3f4f6; border:1px solid #d1d5db; border-radius:8px; cursor:pointer;">💼 1. Management & Operations</button></a>')
|
| 75 |
-
gr.HTML(f'<a href="/client" target="_blank"><button style="width:100%; padding:15px; background-color:#f3f4f6; border:1px solid #d1d5db; border-radius:8px; cursor:pointer;">🤝 2. Client & Strategy</button></a>')
|
| 76 |
-
gr.HTML(f'<a href="/creation" target="_blank"><button style="width:100%; padding:15px; background-color:#f3f4f6; border:1px solid #d1d5db; border-radius:8px; cursor:pointer;">🎨 3. Creation & Content</button></a>')
|
| 77 |
-
with gr.Row():
|
| 78 |
-
gr.HTML(f'<a href="/technical" target="_blank"><button style="width:100%; padding:15px; background-color:#f3f4f6; border:1px solid #d1d5db; border-radius:8px; cursor:pointer;">📊 4. Technical & Performance</button></a>')
|
| 79 |
-
gr.HTML(f'<a href="/data" target="_blank"><button style="width:100%; padding:15px; background-color:#f3f4f6; border:1px solid #d1d5db; border-radius:8px; cursor:pointer;">⚡ 5. Tech & Data</button></a>')
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
inputs=[msg_input, audio_input, lighting_profile, material_profile],
|
| 85 |
-
outputs=[chat_feed, avatar_video_output]
|
| 86 |
-
)
|
| 87 |
|
| 88 |
if __name__ == "__main__":
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
from fastapi import FastAPI, Request
|
| 3 |
+
from pydantic import BaseModel
|
| 4 |
+
from router import route, status
|
| 5 |
+
from state_store import init_db
|
| 6 |
+
from sheets_gateway import get_sheet_answer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
+
app = FastAPI()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
+
class MessageRequest(BaseModel):
|
| 11 |
+
message: str
|
| 12 |
+
level: str = "fully_open"
|
|
|
|
|
|
|
| 13 |
|
| 14 |
+
@app.post("/process")
|
| 15 |
+
@app.post("/webhook/zapier")
|
| 16 |
+
async def handle_request(req: MessageRequest):
|
| 17 |
+
result = route(message=req.message, level=req.level)
|
| 18 |
+
return {"success": True, "response": str(result.get("response", result))}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
+
@app.get("/")
|
| 21 |
+
def root():
|
| 22 |
+
return {"status": "online", "router": status()}
|
| 23 |
|
| 24 |
+
init_db()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
with gr.Blocks() as demo:
|
| 27 |
+
gr.Markdown("# SkyOS MCP Server v2")
|
| 28 |
+
gr.ChatInterface(fn=lambda m, h: str(route(m).get("response", "Error")))
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
if __name__ == "__main__":
|
| 31 |
+
import uvicorn
|
| 32 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
audio_core.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import subprocess
|
| 3 |
-
import sys
|
| 4 |
-
from typing import Dict, Any
|
| 5 |
-
|
| 6 |
-
# Erzwingt die Installation der Pakete direkt im laufenden Container
|
| 7 |
-
try:
|
| 8 |
-
import whisper
|
| 9 |
-
except ImportError:
|
| 10 |
-
subprocess.check_call([sys.executable, "-m", "pip", "install", "openai-whisper", "torch"])
|
| 11 |
-
import whisper
|
| 12 |
-
|
| 13 |
-
try:
|
| 14 |
-
_model = whisper.load_model("base")
|
| 15 |
-
except Exception:
|
| 16 |
-
_model = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
code_runner.py
CHANGED
|
@@ -4,44 +4,24 @@ from diffusers import AutoPipelineForText2Image, LCMScheduler
|
|
| 4 |
model_id = "stabilityai/stable-diffusion-xl-base-1.0"
|
| 5 |
lcm_lora_id = "latent-consistency/lcm-lora-sdxl"
|
| 6 |
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
torch_dtype
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
|
|
|
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
model_id,
|
| 16 |
-
torch_dtype=torch_dtype,
|
| 17 |
-
variant="fp16" if device == "cuda" else None # fp16-Variante läuft auf reinem CPU-Torch oft instabil
|
| 18 |
-
)
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
# Sicherer Transfer auf das verfügbare Gerät
|
| 23 |
-
if device == "cuda":
|
| 24 |
-
pipe.to("cuda")
|
| 25 |
-
else:
|
| 26 |
-
# Überlebenswichtig für CPU Spaces: Reduziert den RAM-Verbrauch drastisch
|
| 27 |
-
print("Optimiere Speicher für CPU-Betrieb...")
|
| 28 |
-
pipe.enable_attention_slicing()
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
# Generierung starten
|
| 37 |
-
image = pipe(
|
| 38 |
-
prompt=prompt,
|
| 39 |
-
num_inference_steps=4,
|
| 40 |
-
guidance_scale=1.5
|
| 41 |
-
).images[0]
|
| 42 |
-
|
| 43 |
-
image.save("sky_output.png")
|
| 44 |
-
print("Bild erfolgreich generiert und als 'sky_output.png' gespeichert.")
|
| 45 |
-
|
| 46 |
-
except Exception as e:
|
| 47 |
-
print(f"Fehler bei der Bildgenerierung in code_runner: {str(e)}")
|
|
|
|
| 4 |
model_id = "stabilityai/stable-diffusion-xl-base-1.0"
|
| 5 |
lcm_lora_id = "latent-consistency/lcm-lora-sdxl"
|
| 6 |
|
| 7 |
+
pipe = AutoPipelineForText2Image.from_pretrained(
|
| 8 |
+
model_id,
|
| 9 |
+
torch_dtype=torch.float16,
|
| 10 |
+
variant="fp16"
|
| 11 |
+
)
|
| 12 |
|
| 13 |
+
pipe.scheduler = LCMScheduler.from_config(pipe.scheduler.config)
|
| 14 |
+
pipe.to("cuda")
|
| 15 |
|
| 16 |
+
pipe.load_lora_weights(lcm_lora_id)
|
| 17 |
+
pipe.fuse_lora()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
prompt = "Sky Meilin, consistent AI persona, creator system, cinematic portrait"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
image = pipe(
|
| 22 |
+
prompt=prompt,
|
| 23 |
+
num_inference_steps=4,
|
| 24 |
+
guidance_scale=1.5
|
| 25 |
+
).images[0]
|
| 26 |
|
| 27 |
+
image.save("sky_output.png")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core/chunker.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from typing import List, Dict, Any, Optional
|
| 2 |
|
|
|
|
| 3 |
class Chunker:
|
| 4 |
"""
|
| 5 |
Splits raw text into chunks for:
|
|
@@ -17,6 +18,7 @@ class Chunker:
|
|
| 17 |
Simple character-based chunking with overlap.
|
| 18 |
Stable baseline for RAG pipelines.
|
| 19 |
"""
|
|
|
|
| 20 |
if not text:
|
| 21 |
return []
|
| 22 |
|
|
@@ -24,18 +26,14 @@ class Chunker:
|
|
| 24 |
start = 0
|
| 25 |
text_length = len(text)
|
| 26 |
|
| 27 |
-
# Absicherung: Schrittweite berechnen und Endlosschleife verhindern
|
| 28 |
-
step = self.chunk_size - self.overlap
|
| 29 |
-
if step <= 0:
|
| 30 |
-
step = 1
|
| 31 |
-
|
| 32 |
while start < text_length:
|
| 33 |
end = start + self.chunk_size
|
| 34 |
chunk = text[start:end]
|
|
|
|
| 35 |
chunks.append(chunk)
|
| 36 |
|
| 37 |
-
#
|
| 38 |
-
start +=
|
| 39 |
|
| 40 |
return chunks
|
| 41 |
|
|
@@ -50,10 +48,12 @@ class Chunker:
|
|
| 50 |
Returns:
|
| 51 |
list of chunked documents with metadata
|
| 52 |
"""
|
|
|
|
| 53 |
chunked_output = []
|
| 54 |
|
| 55 |
for doc in documents:
|
| 56 |
text = doc.get(field, "")
|
|
|
|
| 57 |
chunks = self.chunk_text(text)
|
| 58 |
|
| 59 |
for i, chunk in enumerate(chunks):
|
|
@@ -71,6 +71,7 @@ class Chunker:
|
|
| 71 |
Optional smarter fallback:
|
| 72 |
paragraph-aware chunking.
|
| 73 |
"""
|
|
|
|
| 74 |
paragraphs = text.split("\n\n")
|
| 75 |
chunks = []
|
| 76 |
buffer = ""
|
|
@@ -86,4 +87,4 @@ class Chunker:
|
|
| 86 |
if buffer:
|
| 87 |
chunks.append(buffer.strip())
|
| 88 |
|
| 89 |
-
return chunks
|
|
|
|
| 1 |
from typing import List, Dict, Any, Optional
|
| 2 |
|
| 3 |
+
|
| 4 |
class Chunker:
|
| 5 |
"""
|
| 6 |
Splits raw text into chunks for:
|
|
|
|
| 18 |
Simple character-based chunking with overlap.
|
| 19 |
Stable baseline for RAG pipelines.
|
| 20 |
"""
|
| 21 |
+
|
| 22 |
if not text:
|
| 23 |
return []
|
| 24 |
|
|
|
|
| 26 |
start = 0
|
| 27 |
text_length = len(text)
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
while start < text_length:
|
| 30 |
end = start + self.chunk_size
|
| 31 |
chunk = text[start:end]
|
| 32 |
+
|
| 33 |
chunks.append(chunk)
|
| 34 |
|
| 35 |
+
# move forward with overlap
|
| 36 |
+
start += self.chunk_size - self.overlap
|
| 37 |
|
| 38 |
return chunks
|
| 39 |
|
|
|
|
| 48 |
Returns:
|
| 49 |
list of chunked documents with metadata
|
| 50 |
"""
|
| 51 |
+
|
| 52 |
chunked_output = []
|
| 53 |
|
| 54 |
for doc in documents:
|
| 55 |
text = doc.get(field, "")
|
| 56 |
+
|
| 57 |
chunks = self.chunk_text(text)
|
| 58 |
|
| 59 |
for i, chunk in enumerate(chunks):
|
|
|
|
| 71 |
Optional smarter fallback:
|
| 72 |
paragraph-aware chunking.
|
| 73 |
"""
|
| 74 |
+
|
| 75 |
paragraphs = text.split("\n\n")
|
| 76 |
chunks = []
|
| 77 |
buffer = ""
|
|
|
|
| 87 |
if buffer:
|
| 88 |
chunks.append(buffer.strip())
|
| 89 |
|
| 90 |
+
return chunks
|
core/cleaner.py
CHANGED
|
@@ -2,20 +2,18 @@
|
|
| 2 |
|
| 3 |
import re
|
| 4 |
|
|
|
|
| 5 |
class Cleaner:
|
| 6 |
def clean_text(self, text: str) -> str:
|
| 7 |
-
if not text:
|
| 8 |
-
return ""
|
| 9 |
-
|
| 10 |
text = text.lower()
|
| 11 |
|
| 12 |
-
# URLs entfernen
|
| 13 |
-
text = re.sub(r"http\S+", "", text)
|
| 14 |
|
| 15 |
-
# Sonderzeichen reduzieren
|
| 16 |
-
text = re.sub(r"[^a-zA-Z0-9äöüÄÖÜß\s]", " ", text)
|
| 17 |
|
| 18 |
-
# Mehrfachspaces entfernen
|
| 19 |
-
text = re.sub(r"\s+", " ", text)
|
| 20 |
|
| 21 |
-
return text.strip()
|
|
|
|
| 2 |
|
| 3 |
import re
|
| 4 |
|
| 5 |
+
|
| 6 |
class Cleaner:
|
| 7 |
def clean_text(self, text: str) -> str:
|
|
|
|
|
|
|
|
|
|
| 8 |
text = text.lower()
|
| 9 |
|
| 10 |
+
# URLs entfernen
|
| 11 |
+
text = re.sub(r"http\\S+", "", text)
|
| 12 |
|
| 13 |
+
# Sonderzeichen reduzieren
|
| 14 |
+
text = re.sub(r"[^a-zA-Z0-9äöüÄÖÜß\\s]", " ", text)
|
| 15 |
|
| 16 |
+
# Mehrfachspaces entfernen
|
| 17 |
+
text = re.sub(r"\\s+", " ", text)
|
| 18 |
|
| 19 |
+
return text.strip()
|
core/deepseek.py
CHANGED
|
@@ -5,7 +5,7 @@ def process(message: str) -> str:
|
|
| 5 |
Später kann hier eine echte API- oder Modell-Anbindung eingebaut werden.
|
| 6 |
"""
|
| 7 |
|
| 8 |
-
if message
|
| 9 |
return "DeepSeek: Keine Eingabe erhalten."
|
| 10 |
|
| 11 |
message = str(message).strip()
|
|
@@ -15,4 +15,4 @@ def process(message: str) -> str:
|
|
| 15 |
f"Eingabe empfangen:\n{message}\n\n"
|
| 16 |
"Analyse-Modus: aktiviert.\n"
|
| 17 |
"Status: process() funktioniert."
|
| 18 |
-
)
|
|
|
|
| 5 |
Später kann hier eine echte API- oder Modell-Anbindung eingebaut werden.
|
| 6 |
"""
|
| 7 |
|
| 8 |
+
if not message or not str(message).strip():
|
| 9 |
return "DeepSeek: Keine Eingabe erhalten."
|
| 10 |
|
| 11 |
message = str(message).strip()
|
|
|
|
| 15 |
f"Eingabe empfangen:\n{message}\n\n"
|
| 16 |
"Analyse-Modus: aktiviert.\n"
|
| 17 |
"Status: process() funktioniert."
|
| 18 |
+
)
|
core/embeddings.py
CHANGED
|
@@ -1,13 +1,11 @@
|
|
| 1 |
from sentence_transformers import SentenceTransformer
|
| 2 |
from typing import List, Union
|
| 3 |
-
import numpy as np
|
| 4 |
|
| 5 |
class Embedder:
|
| 6 |
def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
|
| 7 |
-
# Nutzt verfügbare Hardware (Cuda/CPU) automatisch
|
| 8 |
self.model = SentenceTransformer(model_name)
|
| 9 |
|
| 10 |
-
def encode(self, texts: Union[str, List[str]], normalize: bool = True)
|
| 11 |
"""
|
| 12 |
Encodes text(s) into embeddings.
|
| 13 |
|
|
@@ -18,16 +16,9 @@ class Embedder:
|
|
| 18 |
Returns:
|
| 19 |
numpy.ndarray embeddings
|
| 20 |
"""
|
| 21 |
-
if not texts:
|
| 22 |
-
return np.array([])
|
| 23 |
-
|
| 24 |
if isinstance(texts, str):
|
| 25 |
-
if not texts.strip():
|
| 26 |
-
return np.array([])
|
| 27 |
texts = [texts]
|
| 28 |
|
| 29 |
-
# Konvertiert leere Strings in der Liste, um Crashs zu vermeiden
|
| 30 |
-
texts = [t if t.strip() else " " for t in texts]
|
| 31 |
-
|
| 32 |
embeddings = self.model.encode(texts, normalize_embeddings=normalize)
|
| 33 |
return embeddings
|
|
|
|
|
|
| 1 |
from sentence_transformers import SentenceTransformer
|
| 2 |
from typing import List, Union
|
|
|
|
| 3 |
|
| 4 |
class Embedder:
|
| 5 |
def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
|
|
|
|
| 6 |
self.model = SentenceTransformer(model_name)
|
| 7 |
|
| 8 |
+
def encode(self, texts: Union[str, List[str]], normalize: bool = True):
|
| 9 |
"""
|
| 10 |
Encodes text(s) into embeddings.
|
| 11 |
|
|
|
|
| 16 |
Returns:
|
| 17 |
numpy.ndarray embeddings
|
| 18 |
"""
|
|
|
|
|
|
|
|
|
|
| 19 |
if isinstance(texts, str):
|
|
|
|
|
|
|
| 20 |
texts = [texts]
|
| 21 |
|
|
|
|
|
|
|
|
|
|
| 22 |
embeddings = self.model.encode(texts, normalize_embeddings=normalize)
|
| 23 |
return embeddings
|
| 24 |
+
|
core/hermes.py
CHANGED
|
@@ -4,7 +4,7 @@ def process(message: str) -> str:
|
|
| 4 |
Verarbeitet eingehende Nachrichten für SkyOSFullCore.
|
| 5 |
"""
|
| 6 |
|
| 7 |
-
if message
|
| 8 |
return "Hermes: Keine Eingabe erhalten."
|
| 9 |
|
| 10 |
message = str(message).strip()
|
|
@@ -13,4 +13,4 @@ def process(message: str) -> str:
|
|
| 13 |
"Hermes Core ist aktiv.\n\n"
|
| 14 |
f"Eingabe empfangen:\n{message}\n\n"
|
| 15 |
"Status: process() funktioniert."
|
| 16 |
-
)
|
|
|
|
| 4 |
Verarbeitet eingehende Nachrichten für SkyOSFullCore.
|
| 5 |
"""
|
| 6 |
|
| 7 |
+
if not message or not str(message).strip():
|
| 8 |
return "Hermes: Keine Eingabe erhalten."
|
| 9 |
|
| 10 |
message = str(message).strip()
|
|
|
|
| 13 |
"Hermes Core ist aktiv.\n\n"
|
| 14 |
f"Eingabe empfangen:\n{message}\n\n"
|
| 15 |
"Status: process() funktioniert."
|
| 16 |
+
)
|
core/joyai.py
CHANGED
|
@@ -4,7 +4,7 @@ def process(message: str) -> str:
|
|
| 4 |
Kreativ-/Personality-Modul für SkyOSFullCore.
|
| 5 |
"""
|
| 6 |
|
| 7 |
-
if message
|
| 8 |
return "JoyAI: Keine Eingabe erhalten."
|
| 9 |
|
| 10 |
message = str(message).strip()
|
|
@@ -14,4 +14,4 @@ def process(message: str) -> str:
|
|
| 14 |
f"Eingabe empfangen:\n{message}\n\n"
|
| 15 |
"Kreativ-Modus: aktiviert.\n"
|
| 16 |
"Status: process() funktioniert."
|
| 17 |
-
)
|
|
|
|
| 4 |
Kreativ-/Personality-Modul für SkyOSFullCore.
|
| 5 |
"""
|
| 6 |
|
| 7 |
+
if not message or not str(message).strip():
|
| 8 |
return "JoyAI: Keine Eingabe erhalten."
|
| 9 |
|
| 10 |
message = str(message).strip()
|
|
|
|
| 14 |
f"Eingabe empfangen:\n{message}\n\n"
|
| 15 |
"Kreativ-Modus: aktiviert.\n"
|
| 16 |
"Status: process() funktioniert."
|
| 17 |
+
)
|
core/loader.py
CHANGED
|
@@ -3,49 +3,51 @@
|
|
| 3 |
from datasets import load_dataset
|
| 4 |
import os
|
| 5 |
|
|
|
|
| 6 |
class DatasetLoader:
|
|
|
|
| 7 |
def __init__(self):
|
|
|
|
| 8 |
print("DatasetLoader geladen")
|
|
|
|
| 9 |
self.hf_token = os.getenv("HF_TOKEN", None)
|
|
|
|
| 10 |
self.datasets = {
|
| 11 |
"matrix_game": "Skywork/Matrix-Game-3.0",
|
| 12 |
"framework_storage": "sky-meilin/SkyAiFramework-storage"
|
| 13 |
}
|
| 14 |
|
| 15 |
def list_datasets(self):
|
|
|
|
| 16 |
return list(self.datasets.keys())
|
| 17 |
|
| 18 |
-
def load(self, dataset_name: str
|
|
|
|
| 19 |
if dataset_name not in self.datasets:
|
|
|
|
| 20 |
raise ValueError(
|
| 21 |
f"Dataset '{dataset_name}' nicht registriert."
|
| 22 |
)
|
| 23 |
|
| 24 |
dataset_path = self.datasets[dataset_name]
|
| 25 |
-
try:
|
| 26 |
-
# name erlaubt das Laden von Unterordnern/Konfigurationen, falls nötig
|
| 27 |
-
dataset = load_dataset(
|
| 28 |
-
dataset_path,
|
| 29 |
-
name=name,
|
| 30 |
-
token=self.hf_token
|
| 31 |
-
)
|
| 32 |
-
return dataset
|
| 33 |
-
except Exception as e:
|
| 34 |
-
raise RuntimeError(f"Fehler beim Laden von {dataset_name}: {str(e)}")
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
if split is None:
|
| 44 |
-
split = list(dataset.keys())[0]
|
| 45 |
|
| 46 |
-
|
| 47 |
-
raise ValueError(
|
| 48 |
-
f"Split '{split}' existiert nicht. Verfügbar: {list(dataset.keys())}"
|
| 49 |
-
)
|
| 50 |
|
| 51 |
-
return dataset[split]
|
|
|
|
| 3 |
from datasets import load_dataset
|
| 4 |
import os
|
| 5 |
|
| 6 |
+
|
| 7 |
class DatasetLoader:
|
| 8 |
+
|
| 9 |
def __init__(self):
|
| 10 |
+
|
| 11 |
print("DatasetLoader geladen")
|
| 12 |
+
|
| 13 |
self.hf_token = os.getenv("HF_TOKEN", None)
|
| 14 |
+
|
| 15 |
self.datasets = {
|
| 16 |
"matrix_game": "Skywork/Matrix-Game-3.0",
|
| 17 |
"framework_storage": "sky-meilin/SkyAiFramework-storage"
|
| 18 |
}
|
| 19 |
|
| 20 |
def list_datasets(self):
|
| 21 |
+
|
| 22 |
return list(self.datasets.keys())
|
| 23 |
|
| 24 |
+
def load(self, dataset_name: str):
|
| 25 |
+
|
| 26 |
if dataset_name not in self.datasets:
|
| 27 |
+
|
| 28 |
raise ValueError(
|
| 29 |
f"Dataset '{dataset_name}' nicht registriert."
|
| 30 |
)
|
| 31 |
|
| 32 |
dataset_path = self.datasets[dataset_name]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
dataset = load_dataset(
|
| 35 |
+
dataset_path,
|
| 36 |
+
token=self.hf_token
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
return dataset
|
| 40 |
|
| 41 |
+
def load_split(
|
| 42 |
+
self,
|
| 43 |
+
dataset_name: str,
|
| 44 |
+
split: str = None
|
| 45 |
+
):
|
| 46 |
+
|
| 47 |
+
dataset = self.load(dataset_name)
|
| 48 |
|
| 49 |
if split is None:
|
|
|
|
| 50 |
|
| 51 |
+
split = list(dataset.keys())[0]
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
+
return dataset[split]
|
core/orchestrator.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
| 1 |
-
from
|
| 2 |
|
| 3 |
|
| 4 |
class SkyOrchestrator:
|
| 5 |
-
|
| 6 |
def __init__(self):
|
| 7 |
self.modules = {
|
| 8 |
"hermes": hermes,
|
|
@@ -21,25 +20,25 @@ class SkyOrchestrator:
|
|
| 21 |
|
| 22 |
try:
|
| 23 |
return str(module.process(message))
|
|
|
|
| 24 |
except Exception as e:
|
| 25 |
return f"{module_name}: Fehler in process(): {str(e)}"
|
| 26 |
|
| 27 |
def detect_modules(self, message: str) -> list:
|
| 28 |
text = str(message or "").lower()
|
|
|
|
| 29 |
selected = []
|
| 30 |
|
| 31 |
-
# Content / SEO / Business
|
| 32 |
content_keywords = [
|
| 33 |
"blog",
|
| 34 |
"seo",
|
| 35 |
"artikel",
|
| 36 |
"content",
|
| 37 |
-
"contentplan",
|
| 38 |
-
"backlog",
|
| 39 |
"social",
|
| 40 |
"post",
|
| 41 |
"marketing",
|
| 42 |
-
"fanvue"
|
| 43 |
]
|
| 44 |
|
| 45 |
# Technik / System
|
|
@@ -52,7 +51,7 @@ class SkyOrchestrator:
|
|
| 52 |
"python",
|
| 53 |
"telegram",
|
| 54 |
"wordpress",
|
| 55 |
-
"gravatar"
|
| 56 |
]
|
| 57 |
|
| 58 |
# Kreativ / Persona
|
|
@@ -62,77 +61,115 @@ class SkyOrchestrator:
|
|
| 62 |
"persona",
|
| 63 |
"sky",
|
| 64 |
"charakter",
|
| 65 |
-
"creator"
|
| 66 |
]
|
| 67 |
|
| 68 |
# Direkte Modulaufrufe behalten
|
| 69 |
if "hermes" in text:
|
| 70 |
selected.append("hermes")
|
|
|
|
| 71 |
if "deepseek" in text:
|
| 72 |
selected.append("deepseek")
|
|
|
|
| 73 |
if "joy" in text or "joyai" in text:
|
| 74 |
selected.append("joyai")
|
| 75 |
|
| 76 |
# Automatische Intent-Erkennung
|
| 77 |
if any(word in text for word in content_keywords):
|
| 78 |
-
selected.extend([
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
elif any(word in text for word in system_keywords):
|
| 80 |
-
selected.append(
|
|
|
|
|
|
|
|
|
|
| 81 |
elif any(word in text for word in creative_keywords):
|
| 82 |
-
selected.append(
|
|
|
|
|
|
|
| 83 |
|
| 84 |
# Fallback
|
| 85 |
if not selected:
|
| 86 |
-
selected.append(
|
|
|
|
|
|
|
| 87 |
|
| 88 |
return list(dict.fromkeys(selected))
|
| 89 |
|
| 90 |
def process(self, message: str) -> str:
|
|
|
|
| 91 |
if not message or not str(message).strip():
|
| 92 |
return "SkyOrchestrator: Keine Eingabe erhalten."
|
| 93 |
|
| 94 |
-
selected_modules = self.detect_modules(
|
|
|
|
|
|
|
|
|
|
| 95 |
results = []
|
| 96 |
|
| 97 |
for module_name in selected_modules:
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
for k in ["contentplan", "backlog", "template", "storage"]
|
| 105 |
-
):
|
| 106 |
-
knowledge_note = (
|
| 107 |
-
"ℹ️ Info: Anfrage betrifft Knowledge Sources / Contentplan.\n\n"
|
| 108 |
)
|
| 109 |
|
| 110 |
return (
|
| 111 |
"SkyOrchestrator aktiv\n\n"
|
|
|
|
| 112 |
f"Ausgewählte Module:\n"
|
| 113 |
f"{', '.join(selected_modules)}\n\n"
|
| 114 |
-
|
| 115 |
"Antworten:\n\n"
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
| 117 |
)
|
| 118 |
|
| 119 |
def status(self) -> str:
|
|
|
|
| 120 |
active_modules = []
|
|
|
|
| 121 |
for name, module in self.modules.items():
|
| 122 |
-
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
else:
|
| 125 |
-
active_modules.append(f"❌ {name}")
|
| 126 |
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
|
| 130 |
orchestrator = SkyOrchestrator()
|
| 131 |
|
| 132 |
|
| 133 |
def process(message: str) -> str:
|
| 134 |
-
return orchestrator.process(
|
|
|
|
|
|
|
| 135 |
|
| 136 |
|
| 137 |
def status() -> str:
|
| 138 |
-
return orchestrator.status()
|
|
|
|
| 1 |
+
from core import hermes, deepseek, joyai
|
| 2 |
|
| 3 |
|
| 4 |
class SkyOrchestrator:
|
|
|
|
| 5 |
def __init__(self):
|
| 6 |
self.modules = {
|
| 7 |
"hermes": hermes,
|
|
|
|
| 20 |
|
| 21 |
try:
|
| 22 |
return str(module.process(message))
|
| 23 |
+
|
| 24 |
except Exception as e:
|
| 25 |
return f"{module_name}: Fehler in process(): {str(e)}"
|
| 26 |
|
| 27 |
def detect_modules(self, message: str) -> list:
|
| 28 |
text = str(message or "").lower()
|
| 29 |
+
|
| 30 |
selected = []
|
| 31 |
|
| 32 |
+
# Content / SEO / Business
|
| 33 |
content_keywords = [
|
| 34 |
"blog",
|
| 35 |
"seo",
|
| 36 |
"artikel",
|
| 37 |
"content",
|
|
|
|
|
|
|
| 38 |
"social",
|
| 39 |
"post",
|
| 40 |
"marketing",
|
| 41 |
+
"fanvue"
|
| 42 |
]
|
| 43 |
|
| 44 |
# Technik / System
|
|
|
|
| 51 |
"python",
|
| 52 |
"telegram",
|
| 53 |
"wordpress",
|
| 54 |
+
"gravatar"
|
| 55 |
]
|
| 56 |
|
| 57 |
# Kreativ / Persona
|
|
|
|
| 61 |
"persona",
|
| 62 |
"sky",
|
| 63 |
"charakter",
|
| 64 |
+
"creator"
|
| 65 |
]
|
| 66 |
|
| 67 |
# Direkte Modulaufrufe behalten
|
| 68 |
if "hermes" in text:
|
| 69 |
selected.append("hermes")
|
| 70 |
+
|
| 71 |
if "deepseek" in text:
|
| 72 |
selected.append("deepseek")
|
| 73 |
+
|
| 74 |
if "joy" in text or "joyai" in text:
|
| 75 |
selected.append("joyai")
|
| 76 |
|
| 77 |
# Automatische Intent-Erkennung
|
| 78 |
if any(word in text for word in content_keywords):
|
| 79 |
+
selected.extend([
|
| 80 |
+
"hermes",
|
| 81 |
+
"deepseek"
|
| 82 |
+
])
|
| 83 |
+
|
| 84 |
elif any(word in text for word in system_keywords):
|
| 85 |
+
selected.append(
|
| 86 |
+
"hermes"
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
elif any(word in text for word in creative_keywords):
|
| 90 |
+
selected.append(
|
| 91 |
+
"joyai"
|
| 92 |
+
)
|
| 93 |
|
| 94 |
# Fallback
|
| 95 |
if not selected:
|
| 96 |
+
selected.append(
|
| 97 |
+
"hermes"
|
| 98 |
+
)
|
| 99 |
|
| 100 |
return list(dict.fromkeys(selected))
|
| 101 |
|
| 102 |
def process(self, message: str) -> str:
|
| 103 |
+
|
| 104 |
if not message or not str(message).strip():
|
| 105 |
return "SkyOrchestrator: Keine Eingabe erhalten."
|
| 106 |
|
| 107 |
+
selected_modules = self.detect_modules(
|
| 108 |
+
message
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
results = []
|
| 112 |
|
| 113 |
for module_name in selected_modules:
|
| 114 |
+
|
| 115 |
+
results.append(
|
| 116 |
+
self.safe_process(
|
| 117 |
+
module_name,
|
| 118 |
+
message
|
| 119 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
)
|
| 121 |
|
| 122 |
return (
|
| 123 |
"SkyOrchestrator aktiv\n\n"
|
| 124 |
+
|
| 125 |
f"Ausgewählte Module:\n"
|
| 126 |
f"{', '.join(selected_modules)}\n\n"
|
| 127 |
+
|
| 128 |
"Antworten:\n\n"
|
| 129 |
+
|
| 130 |
+
+ "\n\n-------------------\n\n".join(
|
| 131 |
+
results
|
| 132 |
+
)
|
| 133 |
)
|
| 134 |
|
| 135 |
def status(self) -> str:
|
| 136 |
+
|
| 137 |
active_modules = []
|
| 138 |
+
|
| 139 |
for name, module in self.modules.items():
|
| 140 |
+
|
| 141 |
+
if (
|
| 142 |
+
module is not None
|
| 143 |
+
and hasattr(module, "process")
|
| 144 |
+
):
|
| 145 |
+
|
| 146 |
+
active_modules.append(
|
| 147 |
+
f"✅ {name}"
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
else:
|
|
|
|
| 151 |
|
| 152 |
+
active_modules.append(
|
| 153 |
+
f"❌ {name}"
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
return (
|
| 157 |
+
"SkyOSFullCore Orchestrator Status\n\n"
|
| 158 |
+
|
| 159 |
+
+ "\n".join(
|
| 160 |
+
active_modules
|
| 161 |
+
)
|
| 162 |
+
)
|
| 163 |
|
| 164 |
|
| 165 |
orchestrator = SkyOrchestrator()
|
| 166 |
|
| 167 |
|
| 168 |
def process(message: str) -> str:
|
| 169 |
+
return orchestrator.process(
|
| 170 |
+
message
|
| 171 |
+
)
|
| 172 |
|
| 173 |
|
| 174 |
def status() -> str:
|
| 175 |
+
return orchestrator.status()
|
core/router.py
CHANGED
|
@@ -1,17 +1,4 @@
|
|
| 1 |
-
|
| 2 |
-
import sys
|
| 3 |
-
|
| 4 |
-
# Stellt sicher, dass das Hauptverzeichnis im Suchpfad ist, damit router_publish gefunden wird
|
| 5 |
-
main_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
| 6 |
-
if main_dir not in sys.path:
|
| 7 |
-
sys.path.insert(0, main_dir)
|
| 8 |
-
|
| 9 |
-
# Initialisierung der Fallback-Variablen
|
| 10 |
-
can_handle_publish_route = lambda msg: False
|
| 11 |
-
handle_publish_route = lambda msg: "Publish Router nicht verfügbar."
|
| 12 |
-
get_publish_router_status = lambda: "inaktiv (Import-Fehler)"
|
| 13 |
-
PUBLISH_ROUTER_IMPORT_ERROR = None
|
| 14 |
-
|
| 15 |
try:
|
| 16 |
from router_publish import (
|
| 17 |
can_handle_publish_route as _can_handle_publish_route,
|
|
@@ -22,7 +9,6 @@ try:
|
|
| 22 |
can_handle_publish_route = _can_handle_publish_route
|
| 23 |
handle_publish_route = _handle_publish_route
|
| 24 |
get_publish_router_status = _get_publish_router_status
|
| 25 |
-
|
| 26 |
except ImportError as e:
|
| 27 |
PUBLISH_ROUTER_IMPORT_ERROR = f"ImportError: {e}"
|
| 28 |
except Exception as exc:
|
|
|
|
| 1 |
+
# /app/router.py - Dieser Block muss den alten Import ersetzen
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
try:
|
| 3 |
from router_publish import (
|
| 4 |
can_handle_publish_route as _can_handle_publish_route,
|
|
|
|
| 9 |
can_handle_publish_route = _can_handle_publish_route
|
| 10 |
handle_publish_route = _handle_publish_route
|
| 11 |
get_publish_router_status = _get_publish_router_status
|
|
|
|
| 12 |
except ImportError as e:
|
| 13 |
PUBLISH_ROUTER_IMPORT_ERROR = f"ImportError: {e}"
|
| 14 |
except Exception as exc:
|
crm_core.py
CHANGED
|
@@ -1,22 +1,11 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
Version: skyos_crm_core_v2_2026_06_04
|
| 5 |
-
Verarbeitet Leads, Kontakte, Kundenstatus und bereitet Daten für den Sheets-Sync vor.
|
| 6 |
-
"""
|
| 7 |
|
| 8 |
from datetime import datetime
|
| 9 |
-
import json
|
| 10 |
|
| 11 |
-
CRM_CORE_VERSION = "skyos_crm_core_v2_2026_06_04"
|
| 12 |
|
| 13 |
-
|
| 14 |
-
STATE_STORE_AVAILABLE = False
|
| 15 |
-
try:
|
| 16 |
-
from state_store import log_event
|
| 17 |
-
STATE_STORE_AVAILABLE = True
|
| 18 |
-
except Exception:
|
| 19 |
-
pass
|
| 20 |
|
| 21 |
|
| 22 |
def crm_status():
|
|
@@ -68,7 +57,6 @@ def parse_lead_from_text(message: str):
|
|
| 68 |
|
| 69 |
lead = create_lead(notes=text)
|
| 70 |
|
| 71 |
-
# Plattformen-Erkennung
|
| 72 |
if "instagram" in lower:
|
| 73 |
lead["Platform"] = "Instagram"
|
| 74 |
elif "tiktok" in lower:
|
|
@@ -82,7 +70,6 @@ def parse_lead_from_text(message: str):
|
|
| 82 |
elif "gmail" in lower or "email" in lower:
|
| 83 |
lead["Platform"] = "Email"
|
| 84 |
|
| 85 |
-
# Prioritäten-Erkennung
|
| 86 |
if "priorität hoch" in lower or "prio hoch" in lower or "high" in lower:
|
| 87 |
lead["Priority"] = "high"
|
| 88 |
elif "priorität niedrig" in lower or "prio niedrig" in lower or "low" in lower:
|
|
@@ -90,7 +77,6 @@ def parse_lead_from_text(message: str):
|
|
| 90 |
else:
|
| 91 |
lead["Priority"] = "medium"
|
| 92 |
|
| 93 |
-
# Status-Erkennung
|
| 94 |
if "interessiert" in lower:
|
| 95 |
lead["Status"] = "interested"
|
| 96 |
elif "kontaktiert" in lower or "contacted" in lower:
|
|
@@ -100,7 +86,6 @@ def parse_lead_from_text(message: str):
|
|
| 100 |
else:
|
| 101 |
lead["Status"] = "new"
|
| 102 |
|
| 103 |
-
# Angebots-Erkennung
|
| 104 |
if "agency" in lower or "agentur" in lower:
|
| 105 |
lead["Offer"] = "Agency Angebot"
|
| 106 |
elif "crm" in lower:
|
|
@@ -110,16 +95,11 @@ def parse_lead_from_text(message: str):
|
|
| 110 |
elif "fanvue" in lower:
|
| 111 |
lead["Offer"] = "Fanvue / Creator Angebot"
|
| 112 |
|
| 113 |
-
|
| 114 |
-
if ":" in text and "http:" not in lower and "https:" not in lower:
|
| 115 |
after_colon = text.split(":", 1)[1].strip()
|
| 116 |
possible_name = after_colon.split(",", 1)[0].strip()
|
| 117 |
if possible_name:
|
| 118 |
lead["Name"] = possible_name
|
| 119 |
-
elif ":" in text and ("http:" in lower or "https:" in lower):
|
| 120 |
-
parts = text.split()
|
| 121 |
-
if len(parts) > 1 and not parts[1].startswith("http"):
|
| 122 |
-
lead["Name"] = parts[1].replace(",", "")
|
| 123 |
|
| 124 |
return lead
|
| 125 |
|
|
@@ -139,23 +119,11 @@ def process_crm_message(message: str):
|
|
| 139 |
or "followup" in lower
|
| 140 |
or "crm" in lower
|
| 141 |
):
|
| 142 |
-
parsed_lead = parse_lead_from_text(text)
|
| 143 |
-
|
| 144 |
-
# KORRIGIERT: Daten-Sicherung über den State Store, falls verfügbar
|
| 145 |
-
if STATE_STORE_AVAILABLE:
|
| 146 |
-
log_event(
|
| 147 |
-
message=f"Lead-Generierung für {parsed_lead['Name']}",
|
| 148 |
-
context=parsed_lead,
|
| 149 |
-
route_target="crm_core -> sheets_pipeline",
|
| 150 |
-
response="Lead erfolgreich erfasst und im State hinterlegt."
|
| 151 |
-
)
|
| 152 |
-
|
| 153 |
return {
|
| 154 |
"success": True,
|
| 155 |
"action": "create_lead",
|
| 156 |
"crm_core": CRM_CORE_VERSION,
|
| 157 |
-
"lead":
|
| 158 |
-
"sync_status": "state_logged" if STATE_STORE_AVAILABLE else "volatile_memory"
|
| 159 |
}
|
| 160 |
|
| 161 |
return {
|
|
@@ -167,4 +135,4 @@ def process_crm_message(message: str):
|
|
| 167 |
|
| 168 |
|
| 169 |
if __name__ == "__main__":
|
| 170 |
-
print(crm_status())
|
|
|
|
| 1 |
+
# crm_core.py
|
| 2 |
+
# SkyOS CRM Core
|
| 3 |
+
# Version: skyos_crm_core_v2_2026_06_04
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
from datetime import datetime
|
|
|
|
| 6 |
|
|
|
|
| 7 |
|
| 8 |
+
CRM_CORE_VERSION = "skyos_crm_core_v2_2026_06_04"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
|
| 11 |
def crm_status():
|
|
|
|
| 57 |
|
| 58 |
lead = create_lead(notes=text)
|
| 59 |
|
|
|
|
| 60 |
if "instagram" in lower:
|
| 61 |
lead["Platform"] = "Instagram"
|
| 62 |
elif "tiktok" in lower:
|
|
|
|
| 70 |
elif "gmail" in lower or "email" in lower:
|
| 71 |
lead["Platform"] = "Email"
|
| 72 |
|
|
|
|
| 73 |
if "priorität hoch" in lower or "prio hoch" in lower or "high" in lower:
|
| 74 |
lead["Priority"] = "high"
|
| 75 |
elif "priorität niedrig" in lower or "prio niedrig" in lower or "low" in lower:
|
|
|
|
| 77 |
else:
|
| 78 |
lead["Priority"] = "medium"
|
| 79 |
|
|
|
|
| 80 |
if "interessiert" in lower:
|
| 81 |
lead["Status"] = "interested"
|
| 82 |
elif "kontaktiert" in lower or "contacted" in lower:
|
|
|
|
| 86 |
else:
|
| 87 |
lead["Status"] = "new"
|
| 88 |
|
|
|
|
| 89 |
if "agency" in lower or "agentur" in lower:
|
| 90 |
lead["Offer"] = "Agency Angebot"
|
| 91 |
elif "crm" in lower:
|
|
|
|
| 95 |
elif "fanvue" in lower:
|
| 96 |
lead["Offer"] = "Fanvue / Creator Angebot"
|
| 97 |
|
| 98 |
+
if ":" in text:
|
|
|
|
| 99 |
after_colon = text.split(":", 1)[1].strip()
|
| 100 |
possible_name = after_colon.split(",", 1)[0].strip()
|
| 101 |
if possible_name:
|
| 102 |
lead["Name"] = possible_name
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
return lead
|
| 105 |
|
|
|
|
| 119 |
or "followup" in lower
|
| 120 |
or "crm" in lower
|
| 121 |
):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
return {
|
| 123 |
"success": True,
|
| 124 |
"action": "create_lead",
|
| 125 |
"crm_core": CRM_CORE_VERSION,
|
| 126 |
+
"lead": parse_lead_from_text(text)
|
|
|
|
| 127 |
}
|
| 128 |
|
| 129 |
return {
|
|
|
|
| 135 |
|
| 136 |
|
| 137 |
if __name__ == "__main__":
|
| 138 |
+
print(crm_status())
|
extractor.py
CHANGED
|
@@ -2,16 +2,7 @@ from typing import Dict, Any
|
|
| 2 |
|
| 3 |
|
| 4 |
def analyze_context(message: str) -> Dict[str, Any]:
|
| 5 |
-
|
| 6 |
-
text_raw = message.lower()
|
| 7 |
-
|
| 8 |
-
# Satzzeichen entfernen für exaktes Word-Matching
|
| 9 |
-
clean_text = text_raw
|
| 10 |
-
for char in [".", ",", "!", "?", ";", ":", "-", "_", "(", ")", "[", "]", "\n", "\r"]:
|
| 11 |
-
clean_text = clean_text.replace(char, " ")
|
| 12 |
-
|
| 13 |
-
# In einzelne Wörter splitten, um Sub-String-Fehler (z.B. "ich" in "sicherlich") zu vermeiden
|
| 14 |
-
words_set = set(clean_text.split())
|
| 15 |
|
| 16 |
scores = {
|
| 17 |
"Business": 0.0,
|
|
@@ -40,21 +31,20 @@ def analyze_context(message: str) -> Dict[str, Any]:
|
|
| 40 |
"ziel", "entscheidung", "gedanke"
|
| 41 |
]
|
| 42 |
|
| 43 |
-
# Abgleich auf echte Wort-Treffer oder exakte Phrasen
|
| 44 |
for word in business_words:
|
| 45 |
-
if word in
|
| 46 |
scores["Business"] += 1
|
| 47 |
|
| 48 |
for word in tech_words:
|
| 49 |
-
if word in
|
| 50 |
scores["Technik"] += 1
|
| 51 |
|
| 52 |
for word in creative_words:
|
| 53 |
-
if word in
|
| 54 |
scores["Kreativ"] += 1
|
| 55 |
|
| 56 |
for word in personal_words:
|
| 57 |
-
if word in
|
| 58 |
scores["Persönlich"] += 0.5
|
| 59 |
|
| 60 |
total = sum(scores.values()) or 1
|
|
@@ -64,28 +54,24 @@ def analyze_context(message: str) -> Dict[str, Any]:
|
|
| 64 |
for key, value in scores.items()
|
| 65 |
}
|
| 66 |
|
| 67 |
-
|
| 68 |
-
actual_confidence = distribution.get(raw_primary, 0.5)
|
| 69 |
|
| 70 |
-
|
| 71 |
-
if distribution[raw_primary] < 0.4:
|
| 72 |
primary = "Mixed"
|
| 73 |
-
else:
|
| 74 |
-
primary = raw_primary
|
| 75 |
|
| 76 |
-
intent = detect_intent(
|
| 77 |
-
complexity = detect_complexity(
|
| 78 |
|
| 79 |
return {
|
| 80 |
"primary_context": {
|
| 81 |
"type": primary,
|
| 82 |
-
"confidence":
|
| 83 |
},
|
| 84 |
"context_distribution": distribution,
|
| 85 |
"intent_layer": intent,
|
| 86 |
"complexity_level": complexity,
|
| 87 |
"routing_hint": build_routing_hint(primary, intent, complexity),
|
| 88 |
-
"reasoning": "Kontext wurde anhand
|
| 89 |
}
|
| 90 |
|
| 91 |
|
|
@@ -113,8 +99,10 @@ def detect_complexity(text: str) -> str:
|
|
| 113 |
|
| 114 |
if length > 1500:
|
| 115 |
return "high"
|
|
|
|
| 116 |
if length > 500:
|
| 117 |
return "medium"
|
|
|
|
| 118 |
return "low"
|
| 119 |
|
| 120 |
|
|
@@ -136,4 +124,4 @@ def build_routing_hint(primary: str, intent: Dict[str, Any], complexity: str) ->
|
|
| 136 |
"target_system": target,
|
| 137 |
"priority": priority,
|
| 138 |
"intent": intent["type"]
|
| 139 |
-
}
|
|
|
|
| 2 |
|
| 3 |
|
| 4 |
def analyze_context(message: str) -> Dict[str, Any]:
|
| 5 |
+
text = message.lower()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
scores = {
|
| 8 |
"Business": 0.0,
|
|
|
|
| 31 |
"ziel", "entscheidung", "gedanke"
|
| 32 |
]
|
| 33 |
|
|
|
|
| 34 |
for word in business_words:
|
| 35 |
+
if word in text:
|
| 36 |
scores["Business"] += 1
|
| 37 |
|
| 38 |
for word in tech_words:
|
| 39 |
+
if word in text:
|
| 40 |
scores["Technik"] += 1
|
| 41 |
|
| 42 |
for word in creative_words:
|
| 43 |
+
if word in text:
|
| 44 |
scores["Kreativ"] += 1
|
| 45 |
|
| 46 |
for word in personal_words:
|
| 47 |
+
if word in text:
|
| 48 |
scores["Persönlich"] += 0.5
|
| 49 |
|
| 50 |
total = sum(scores.values()) or 1
|
|
|
|
| 54 |
for key, value in scores.items()
|
| 55 |
}
|
| 56 |
|
| 57 |
+
primary = max(distribution, key=distribution.get)
|
|
|
|
| 58 |
|
| 59 |
+
if distribution[primary] < 0.4:
|
|
|
|
| 60 |
primary = "Mixed"
|
|
|
|
|
|
|
| 61 |
|
| 62 |
+
intent = detect_intent(text)
|
| 63 |
+
complexity = detect_complexity(text)
|
| 64 |
|
| 65 |
return {
|
| 66 |
"primary_context": {
|
| 67 |
"type": primary,
|
| 68 |
+
"confidence": distribution.get(primary, 0.5) if primary != "Mixed" else 0.5
|
| 69 |
},
|
| 70 |
"context_distribution": distribution,
|
| 71 |
"intent_layer": intent,
|
| 72 |
"complexity_level": complexity,
|
| 73 |
"routing_hint": build_routing_hint(primary, intent, complexity),
|
| 74 |
+
"reasoning": "Kontext wurde anhand erkannter Schlüsselwörter und Signalgruppen bestimmt."
|
| 75 |
}
|
| 76 |
|
| 77 |
|
|
|
|
| 99 |
|
| 100 |
if length > 1500:
|
| 101 |
return "high"
|
| 102 |
+
|
| 103 |
if length > 500:
|
| 104 |
return "medium"
|
| 105 |
+
|
| 106 |
return "low"
|
| 107 |
|
| 108 |
|
|
|
|
| 124 |
"target_system": target,
|
| 125 |
"priority": priority,
|
| 126 |
"intent": intent["type"]
|
| 127 |
+
}
|
fanvue_guide.py
CHANGED
|
@@ -13,21 +13,6 @@ FANVUE_VERIFICATION = "https://fanvue.com/settings/verification"
|
|
| 13 |
FANVUE_PAYOUTS = "https://fanvue.com/settings/payouts"
|
| 14 |
|
| 15 |
|
| 16 |
-
def clean_username(name: str) -> str:
|
| 17 |
-
"""Bereinigt den Namen für gültige Plattform-Usernames (keine Umlaute/Sonderzeichen)."""
|
| 18 |
-
username = name.lower().strip()
|
| 19 |
-
# Umlaute ersetzen
|
| 20 |
-
replacements = {"ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss"}
|
| 21 |
-
for char, rep in replacements.items():
|
| 22 |
-
username = username.replace(char, rep)
|
| 23 |
-
|
| 24 |
-
# Leerzeichen und Bindestriche durch Unterstriche ersetzen
|
| 25 |
-
username = username.replace(" ", "_").replace("-", "_")
|
| 26 |
-
|
| 27 |
-
# Erlaubte Zeichen filtern (nur Alphanumerisch und Unterstriche)
|
| 28 |
-
return "".join(c for c in username if c.isalnum() or c == "_")
|
| 29 |
-
|
| 30 |
-
|
| 31 |
def get_fanvue_guide(creator_name: str = "Creator") -> str:
|
| 32 |
"""
|
| 33 |
Gibt eine vollständige, personalisierte Fanvue Setup-Anleitung zurück.
|
|
@@ -39,7 +24,6 @@ def get_fanvue_guide(creator_name: str = "Creator") -> str:
|
|
| 39 |
Vollständige Schritt-für-Schritt Anleitung als String.
|
| 40 |
"""
|
| 41 |
name = creator_name.strip() if creator_name else "Creator"
|
| 42 |
-
suggested_username = clean_username(name)
|
| 43 |
|
| 44 |
guide = f"""Fanvue Setup Guide — {name}
|
| 45 |
Version: {MODULE_VERSION}
|
|
@@ -60,7 +44,7 @@ SCHRITT 2 — Profil einrichten
|
|
| 60 |
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 61 |
|
| 62 |
1. Oben rechts auf Avatar klicken → "Edit Profile"
|
| 63 |
-
2. Username eingeben: {
|
| 64 |
3. Display Name: {name}
|
| 65 |
4. Bio: Dein Hook-Text (2 Sätze — wer du bist + was Subscriber erwartet)
|
| 66 |
5. Profilbild hochladen (empfohlen: quadratisch, min. 400×400px)
|
|
@@ -140,6 +124,7 @@ def process(message: str) -> str:
|
|
| 140 |
Fanvue Guide als String.
|
| 141 |
"""
|
| 142 |
name = "Creator"
|
|
|
|
| 143 |
msg = message.lower() if message else ""
|
| 144 |
|
| 145 |
if "molina" in msg:
|
|
@@ -151,4 +136,4 @@ def process(message: str) -> str:
|
|
| 151 |
|
| 152 |
|
| 153 |
if __name__ == "__main__":
|
| 154 |
-
print(get_fanvue_guide("Molina Hill"))
|
|
|
|
| 13 |
FANVUE_PAYOUTS = "https://fanvue.com/settings/payouts"
|
| 14 |
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
def get_fanvue_guide(creator_name: str = "Creator") -> str:
|
| 17 |
"""
|
| 18 |
Gibt eine vollständige, personalisierte Fanvue Setup-Anleitung zurück.
|
|
|
|
| 24 |
Vollständige Schritt-für-Schritt Anleitung als String.
|
| 25 |
"""
|
| 26 |
name = creator_name.strip() if creator_name else "Creator"
|
|
|
|
| 27 |
|
| 28 |
guide = f"""Fanvue Setup Guide — {name}
|
| 29 |
Version: {MODULE_VERSION}
|
|
|
|
| 44 |
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 45 |
|
| 46 |
1. Oben rechts auf Avatar klicken → "Edit Profile"
|
| 47 |
+
2. Username eingeben: {name.lower().replace(" ", "_")}
|
| 48 |
3. Display Name: {name}
|
| 49 |
4. Bio: Dein Hook-Text (2 Sätze — wer du bist + was Subscriber erwartet)
|
| 50 |
5. Profilbild hochladen (empfohlen: quadratisch, min. 400×400px)
|
|
|
|
| 124 |
Fanvue Guide als String.
|
| 125 |
"""
|
| 126 |
name = "Creator"
|
| 127 |
+
|
| 128 |
msg = message.lower() if message else ""
|
| 129 |
|
| 130 |
if "molina" in msg:
|
|
|
|
| 136 |
|
| 137 |
|
| 138 |
if __name__ == "__main__":
|
| 139 |
+
print(get_fanvue_guide("Molina Hill"))
|
mcp_server.py
CHANGED
|
@@ -1,40 +1,19 @@
|
|
| 1 |
-
"""
|
| 2 |
-
mcp_server.py
|
| 3 |
-
SkyOS Model Context Protocol (MCP) Server
|
| 4 |
-
Stellt LLM-Agenten (wie Claude oder DeepSeek) Werkzeuge für das Creator OS bereit.
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
import json
|
| 8 |
from mcp.server.fastmcp import FastMCP
|
| 9 |
-
|
| 10 |
-
# Core-System-Imports
|
| 11 |
-
from router import route, status
|
| 12 |
-
from trend_predictor import skyos_trend_score
|
| 13 |
-
from crm_core import process_crm_message
|
| 14 |
|
| 15 |
# Initialisierung des MCP-Servers
|
| 16 |
mcp = FastMCP("SkyOS_MCP_Server")
|
| 17 |
|
| 18 |
-
|
| 19 |
@mcp.tool()
|
| 20 |
def route_request(message: str) -> str:
|
| 21 |
-
"""
|
| 22 |
-
|
| 23 |
-
Verarbeitet Text-Intents und steuert die Sub-Module an.
|
| 24 |
-
"""
|
| 25 |
-
result = route(message)
|
| 26 |
-
# Falls das Ergebnis ein Dictionary/Objekt ist, sauber als JSON-String zurückgeben
|
| 27 |
-
if isinstance(result, (dict, list)):
|
| 28 |
-
return json.dumps(result, ensure_ascii=False)
|
| 29 |
-
return str(result)
|
| 30 |
-
|
| 31 |
|
| 32 |
@mcp.tool()
|
| 33 |
def get_system_status() -> str:
|
| 34 |
"""Gibt den aktuellen Status des SkyOS-Systems zurück."""
|
| 35 |
return status()
|
| 36 |
|
| 37 |
-
|
| 38 |
@mcp.tool()
|
| 39 |
def analyze_trend(
|
| 40 |
name: str,
|
|
@@ -44,28 +23,19 @@ def analyze_trend(
|
|
| 44 |
narrative: float,
|
| 45 |
saturation: float
|
| 46 |
) -> dict:
|
| 47 |
-
"""
|
| 48 |
-
Führt eine mathematische Trend-Analyse über den skyos_trend_score aus.
|
| 49 |
-
Berechnet Virality Index und Aufstiegswahrscheinlichkeiten für Contentpläne.
|
| 50 |
-
"""
|
| 51 |
return skyos_trend_score(name, share_rate, comment_depth, hook_score, narrative, saturation)
|
| 52 |
|
| 53 |
-
|
| 54 |
@mcp.tool()
|
| 55 |
-
def crm_core_access(command: str) ->
|
| 56 |
-
"""
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
"""
|
| 60 |
-
return process_crm_message(command)
|
| 61 |
-
|
| 62 |
|
| 63 |
@mcp.tool()
|
| 64 |
def skyos_process_trigger(action: str) -> str:
|
| 65 |
-
"""Trigger für skyos_process zum Starten von
|
| 66 |
-
return f"SkyOS Process: Aktion '{action}'
|
| 67 |
-
|
| 68 |
|
| 69 |
if __name__ == "__main__":
|
| 70 |
-
# Startet den MCP Server im Standard-Transportmodus (stdio)
|
| 71 |
mcp.run()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from mcp.server.fastmcp import FastMCP
|
| 2 |
+
from router import route, status, skyos_trend_score
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
# Initialisierung des MCP-Servers
|
| 5 |
mcp = FastMCP("SkyOS_MCP_Server")
|
| 6 |
|
|
|
|
| 7 |
@mcp.tool()
|
| 8 |
def route_request(message: str) -> str:
|
| 9 |
+
"""Leitet Anfragen durch den SkyOS Router."""
|
| 10 |
+
return str(route(message))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
@mcp.tool()
|
| 13 |
def get_system_status() -> str:
|
| 14 |
"""Gibt den aktuellen Status des SkyOS-Systems zurück."""
|
| 15 |
return status()
|
| 16 |
|
|
|
|
| 17 |
@mcp.tool()
|
| 18 |
def analyze_trend(
|
| 19 |
name: str,
|
|
|
|
| 23 |
narrative: float,
|
| 24 |
saturation: float
|
| 25 |
) -> dict:
|
| 26 |
+
"""Führt eine Trend-Analyse über den skyos_trend_score aus."""
|
|
|
|
|
|
|
|
|
|
| 27 |
return skyos_trend_score(name, share_rate, comment_depth, hook_score, narrative, saturation)
|
| 28 |
|
|
|
|
| 29 |
@mcp.tool()
|
| 30 |
+
def crm_core_access(command: str) -> str:
|
| 31 |
+
"""Schnittstelle zum CRM Core Modul."""
|
| 32 |
+
# Hier wird die direkte Anbindung an den CRM Core abgebildet
|
| 33 |
+
return f"CRM Core: Befehl '{command}' verarbeitet."
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
@mcp.tool()
|
| 36 |
def skyos_process_trigger(action: str) -> str:
|
| 37 |
+
"""Trigger für skyos_process zum Starten von Workflows."""
|
| 38 |
+
return f"SkyOS Process: Aktion '{action}' eingeleitet."
|
|
|
|
| 39 |
|
| 40 |
if __name__ == "__main__":
|
|
|
|
| 41 |
mcp.run()
|
modules/adapters/base_adapter.py
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
base_adapter.py
|
| 3 |
-
|
| 4 |
-
Basisklasse aller Sync-Adapter.
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
from __future__ import annotations
|
| 8 |
-
|
| 9 |
-
from abc import ABC, abstractmethod
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
class BaseAdapter(ABC):
|
| 13 |
-
|
| 14 |
-
@property
|
| 15 |
-
@abstractmethod
|
| 16 |
-
def name(self) -> str:
|
| 17 |
-
...
|
| 18 |
-
|
| 19 |
-
@abstractmethod
|
| 20 |
-
def connect(self) -> bool:
|
| 21 |
-
...
|
| 22 |
-
|
| 23 |
-
@abstractmethod
|
| 24 |
-
def pull(self):
|
| 25 |
-
...
|
| 26 |
-
|
| 27 |
-
@abstractmethod
|
| 28 |
-
def push(self, data):
|
| 29 |
-
...
|
| 30 |
-
|
| 31 |
-
@abstractmethod
|
| 32 |
-
def sync(self):
|
| 33 |
-
...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/adapters/huggingface_adapter.py
DELETED
|
@@ -1,154 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
huggingface_adapter.py
|
| 3 |
-
SkyOS Hugging Face Adapter
|
| 4 |
-
|
| 5 |
-
Adapter für die Integration von Hugging Face
|
| 6 |
-
Modellen und APIs in den SkyOS Sync Manager / Core.
|
| 7 |
-
|
| 8 |
-
Autor: Sky Meilin
|
| 9 |
-
Projekt: SkyOS
|
| 10 |
-
"""
|
| 11 |
-
|
| 12 |
-
from __future__ import annotations
|
| 13 |
-
|
| 14 |
-
import os
|
| 15 |
-
from ..logger import logger
|
| 16 |
-
from .base_adapter import BaseAdapter
|
| 17 |
-
|
| 18 |
-
# Offizielles Hugging Face SDK
|
| 19 |
-
try:
|
| 20 |
-
from huggingface_hub import InferenceClient
|
| 21 |
-
except ImportError:
|
| 22 |
-
InferenceClient = None
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
class HuggingFaceAdapter(BaseAdapter):
|
| 26 |
-
"""
|
| 27 |
-
Adapter zur Kommunikation mit Hugging Face APIs und Modellen.
|
| 28 |
-
"""
|
| 29 |
-
|
| 30 |
-
def __init__(self, model_id: str = "meta-llama/Llama-3-8B-Instruct") -> None:
|
| 31 |
-
self._connected = False
|
| 32 |
-
self.model_id = model_id
|
| 33 |
-
self.client: InferenceClient | None = None
|
| 34 |
-
|
| 35 |
-
# ---------------------------------------------------------
|
| 36 |
-
# Eigenschaften
|
| 37 |
-
# ---------------------------------------------------------
|
| 38 |
-
|
| 39 |
-
@property
|
| 40 |
-
def name(self) -> str:
|
| 41 |
-
return "Hugging Face"
|
| 42 |
-
|
| 43 |
-
@property
|
| 44 |
-
def connected(self) -> bool:
|
| 45 |
-
return self._connected
|
| 46 |
-
|
| 47 |
-
# ---------------------------------------------------------
|
| 48 |
-
# Verbindung
|
| 49 |
-
# ---------------------------------------------------------
|
| 50 |
-
|
| 51 |
-
def connect(self) -> bool:
|
| 52 |
-
"""
|
| 53 |
-
Initialisiert den Hugging Face Inference Client mit dem API-Token.
|
| 54 |
-
"""
|
| 55 |
-
if InferenceClient is None:
|
| 56 |
-
logger.error(
|
| 57 |
-
"%s: 'huggingface_hub' Bibliothek ist nicht installiert. "
|
| 58 |
-
"Bitte installiere sie mit 'pip install huggingface_hub'.",
|
| 59 |
-
self.name
|
| 60 |
-
)
|
| 61 |
-
self._connected = False
|
| 62 |
-
return False
|
| 63 |
-
|
| 64 |
-
# Sucht das Token aus den Umgebungsvariablen (z.B. in deiner .env)
|
| 65 |
-
hf_token = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_CO_API_TOKEN")
|
| 66 |
-
|
| 67 |
-
try:
|
| 68 |
-
# Verbindung wird mit dem Client-Objekt aufgebaut
|
| 69 |
-
self.client = InferenceClient(model=self.model_id, token=hf_token)
|
| 70 |
-
self._connected = True
|
| 71 |
-
|
| 72 |
-
logger.info(
|
| 73 |
-
"%s verbunden. Standard-Modell: %s (Token vorhanden: %s)",
|
| 74 |
-
self.name,
|
| 75 |
-
self.model_id,
|
| 76 |
-
"Ja" if hf_token else "Nein (Öffentliche API)"
|
| 77 |
-
)
|
| 78 |
-
return True
|
| 79 |
-
|
| 80 |
-
except Exception as error:
|
| 81 |
-
logger.exception(
|
| 82 |
-
"%s Verbindung fehlgeschlagen: %s",
|
| 83 |
-
self.name,
|
| 84 |
-
error
|
| 85 |
-
)
|
| 86 |
-
self._connected = False
|
| 87 |
-
return False
|
| 88 |
-
|
| 89 |
-
def disconnect(self) -> None:
|
| 90 |
-
"""
|
| 91 |
-
Trennt die Verbindung zum Client.
|
| 92 |
-
"""
|
| 93 |
-
self.client = None
|
| 94 |
-
self._connected = False
|
| 95 |
-
logger.info("%s getrennt.", self.name)
|
| 96 |
-
|
| 97 |
-
def is_connected(self) -> bool:
|
| 98 |
-
"""
|
| 99 |
-
Gibt den Verbindungsstatus zurück.
|
| 100 |
-
"""
|
| 101 |
-
return self._connected
|
| 102 |
-
|
| 103 |
-
# ---------------------------------------------------------
|
| 104 |
-
# Inference Funktionalität (Schnittstelle für den Core)
|
| 105 |
-
# ---------------------------------------------------------
|
| 106 |
-
|
| 107 |
-
def generate_text(self, prompt: str, **kwargs) -> str | None:
|
| 108 |
-
"""
|
| 109 |
-
Sendet einen Prompt an das geladene Hugging Face Modell.
|
| 110 |
-
"""
|
| 111 |
-
if not self._connected or not self.client:
|
| 112 |
-
logger.warning("%s: Textgenerierung nicht möglich – nicht verbunden.", self.name)
|
| 113 |
-
return None
|
| 114 |
-
|
| 115 |
-
try:
|
| 116 |
-
# Nutzt die Standard-Textgenerierung des Inference Clients
|
| 117 |
-
response = self.client.text_generation(prompt, **kwargs)
|
| 118 |
-
return response
|
| 119 |
-
except Exception as error:
|
| 120 |
-
logger.error("%s Fehler bei der Textgenerierung: %s", self.name, error)
|
| 121 |
-
return None
|
| 122 |
-
|
| 123 |
-
# ---------------------------------------------------------
|
| 124 |
-
# BaseAdapter (Synchronisation)
|
| 125 |
-
# ---------------------------------------------------------
|
| 126 |
-
|
| 127 |
-
def pull(self) -> dict | None:
|
| 128 |
-
"""
|
| 129 |
-
Phase 2: Status-Check des Modells oder Abruf von Metadaten über das Repository.
|
| 130 |
-
"""
|
| 131 |
-
if not self._connected or not self.client:
|
| 132 |
-
return None
|
| 133 |
-
|
| 134 |
-
try:
|
| 135 |
-
# Beispielhafter Pull: Modell-Informationen abrufen
|
| 136 |
-
model_info = {"model_id": self.model_id, "status": "aktiv/bereit"}
|
| 137 |
-
return model_info
|
| 138 |
-
except Exception as error:
|
| 139 |
-
logger.error("%s Fehler beim Pull der Modell-Daten: %s", self.name, error)
|
| 140 |
-
return None
|
| 141 |
-
|
| 142 |
-
def push(self, data: dict) -> bool:
|
| 143 |
-
"""
|
| 144 |
-
Phase 3: Falls nötig, Konfigurationen oder Logs an Hugging Face Repositories
|
| 145 |
-
oder Spaces senden (Standardmäßig auf True gesetzt).
|
| 146 |
-
"""
|
| 147 |
-
return True
|
| 148 |
-
|
| 149 |
-
def sync(self) -> bool:
|
| 150 |
-
"""
|
| 151 |
-
Prüft die Erreichbarkeit des Modells.
|
| 152 |
-
"""
|
| 153 |
-
logger.info("Starte Sync für %s...", self.name)
|
| 154 |
-
return self.connect()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/adapters/init.py
DELETED
|
@@ -1,24 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
SkyOS Sync Adapters
|
| 3 |
-
"""
|
| 4 |
-
|
| 5 |
-
from .sync_manager import SyncManager
|
| 6 |
-
from .registry import SyncRegistry
|
| 7 |
-
from .scheduler import SyncScheduler
|
| 8 |
-
from .conflict_resolver import ConflictResolver
|
| 9 |
-
|
| 10 |
-
from .adapters import (
|
| 11 |
-
BaseAdapter,
|
| 12 |
-
GoogleSheetsAdapter,
|
| 13 |
-
HuggingFaceAdapter,
|
| 14 |
-
)
|
| 15 |
-
|
| 16 |
-
__all__ = [
|
| 17 |
-
"SyncManager",
|
| 18 |
-
"SyncRegistry",
|
| 19 |
-
"SyncScheduler",
|
| 20 |
-
"ConflictResolver",
|
| 21 |
-
"BaseAdapter",
|
| 22 |
-
"GoogleSheetsAdapter",
|
| 23 |
-
"HuggingFaceAdapter",
|
| 24 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/adapters/sheets_adapter.py
DELETED
|
@@ -1,175 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
sheets_adapter.py
|
| 3 |
-
SkyOS Google Sheets Adapter
|
| 4 |
-
|
| 5 |
-
Adapter für die Anbindung von Google Sheets
|
| 6 |
-
an den SkyOS Sync Manager.
|
| 7 |
-
|
| 8 |
-
Autor: Sky Meilin
|
| 9 |
-
Projekt: SkyOS
|
| 10 |
-
"""
|
| 11 |
-
|
| 12 |
-
from __future__ import annotations
|
| 13 |
-
|
| 14 |
-
import logging
|
| 15 |
-
|
| 16 |
-
from ..logger import logger
|
| 17 |
-
from .base_adapter import BaseAdapter
|
| 18 |
-
|
| 19 |
-
# Vorhandene Google-Module von SkyOS
|
| 20 |
-
from ...Google.auth import GoogleAuth
|
| 21 |
-
from ...Google.sheets_gateway import SheetsGateway
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
class GoogleSheetsAdapter(BaseAdapter):
|
| 25 |
-
"""
|
| 26 |
-
Adapter für Google Sheets.
|
| 27 |
-
"""
|
| 28 |
-
|
| 29 |
-
def __init__(self, spreadsheet_id: str | None = None) -> None:
|
| 30 |
-
self._connected = False
|
| 31 |
-
self.auth = GoogleAuth()
|
| 32 |
-
self.gateway = SheetsGateway(self.auth)
|
| 33 |
-
# Die ID der Google Sheet-Datei, die synchronisiert werden soll
|
| 34 |
-
self.spreadsheet_id = spreadsheet_id
|
| 35 |
-
|
| 36 |
-
# ---------------------------------------------------------
|
| 37 |
-
# Eigenschaften
|
| 38 |
-
# ---------------------------------------------------------
|
| 39 |
-
|
| 40 |
-
@property
|
| 41 |
-
def name(self) -> str:
|
| 42 |
-
return "Google Sheets"
|
| 43 |
-
|
| 44 |
-
@property
|
| 45 |
-
def connected(self) -> bool:
|
| 46 |
-
return self._connected
|
| 47 |
-
|
| 48 |
-
# ---------------------------------------------------------
|
| 49 |
-
# Verbindung
|
| 50 |
-
# ---------------------------------------------------------
|
| 51 |
-
|
| 52 |
-
def connect(self) -> bool:
|
| 53 |
-
"""
|
| 54 |
-
Baut die Verbindung zu Google auf.
|
| 55 |
-
"""
|
| 56 |
-
try:
|
| 57 |
-
self.auth.authenticate()
|
| 58 |
-
self.gateway.connect()
|
| 59 |
-
self._connected = True
|
| 60 |
-
|
| 61 |
-
logger.info(
|
| 62 |
-
"%s verbunden.",
|
| 63 |
-
self.name,
|
| 64 |
-
)
|
| 65 |
-
return True
|
| 66 |
-
|
| 67 |
-
except Exception as error:
|
| 68 |
-
logger.exception(
|
| 69 |
-
"%s Verbindung fehlgeschlagen: %s",
|
| 70 |
-
self.name,
|
| 71 |
-
error,
|
| 72 |
-
)
|
| 73 |
-
self._connected = False
|
| 74 |
-
return False
|
| 75 |
-
|
| 76 |
-
def disconnect(self) -> None:
|
| 77 |
-
"""
|
| 78 |
-
Trennt die Verbindung.
|
| 79 |
-
"""
|
| 80 |
-
self._connected = False
|
| 81 |
-
logger.info(
|
| 82 |
-
"%s getrennt.",
|
| 83 |
-
self.name,
|
| 84 |
-
)
|
| 85 |
-
|
| 86 |
-
def is_connected(self) -> bool:
|
| 87 |
-
"""
|
| 88 |
-
Verbindungsstatus.
|
| 89 |
-
"""
|
| 90 |
-
return self._connected
|
| 91 |
-
|
| 92 |
-
# ---------------------------------------------------------
|
| 93 |
-
# Informationen
|
| 94 |
-
# ---------------------------------------------------------
|
| 95 |
-
|
| 96 |
-
def list_sheets(self) -> list[str]:
|
| 97 |
-
"""
|
| 98 |
-
Gibt alle Tabellenblätter (Locations) zurück.
|
| 99 |
-
"""
|
| 100 |
-
if not self._connected or not self.spreadsheet_id:
|
| 101 |
-
return []
|
| 102 |
-
|
| 103 |
-
try:
|
| 104 |
-
# Holt alle Tabellenblatt-Namen der aktuellen Datei
|
| 105 |
-
return self.gateway.get_sheet_names(self.spreadsheet_id)
|
| 106 |
-
except Exception as error:
|
| 107 |
-
logger.error("Fehler beim Abrufen der Tabellenblätter: %s", error)
|
| 108 |
-
return []
|
| 109 |
-
|
| 110 |
-
# ---------------------------------------------------------
|
| 111 |
-
# BaseAdapter (Synchronisation)
|
| 112 |
-
# ---------------------------------------------------------
|
| 113 |
-
|
| 114 |
-
def pull(self) -> dict[str, list[list]] | None:
|
| 115 |
-
"""
|
| 116 |
-
Phase 2: Daten aus den Google Sheets (nach Locations/Tabs aufgeteilt) abrufen.
|
| 117 |
-
Gibt ein Dictionary zurück: { "Location_Name": [[Zeile1], [Zeile2], ...] }
|
| 118 |
-
"""
|
| 119 |
-
if not self._connected or not self.spreadsheet_id:
|
| 120 |
-
logger.warning("%s: Pull nicht möglich - keine Verbindung oder Spreadsheet-ID.", self.name)
|
| 121 |
-
return None
|
| 122 |
-
|
| 123 |
-
try:
|
| 124 |
-
sheets = self.list_sheets()
|
| 125 |
-
pulled_data = {}
|
| 126 |
-
|
| 127 |
-
for sheet_name in sheets:
|
| 128 |
-
# Liest alle Daten aus dem jeweiligen Tabellenblatt (der Location)
|
| 129 |
-
data = self.gateway.read_values(self.spreadsheet_id, range_name=f"'{sheet_name}'!A:Z")
|
| 130 |
-
pulled_data[sheet_name] = data
|
| 131 |
-
logger.debug("Daten für Location '%s' erfolgreich gepullt.", sheet_name)
|
| 132 |
-
|
| 133 |
-
return pulled_data
|
| 134 |
-
|
| 135 |
-
except Exception as error:
|
| 136 |
-
logger.exception("Fehler beim Pull der Google Sheets Daten: %s", error)
|
| 137 |
-
return None
|
| 138 |
-
|
| 139 |
-
def push(self, data: dict[str, list[list]]) -> bool:
|
| 140 |
-
"""
|
| 141 |
-
Phase 3: Synchronisierte Daten zurück in die Google Sheets schreiben.
|
| 142 |
-
Erwartet Daten im Format: { "Location_Name": [[Zeile1], [Zeile2], ...] }
|
| 143 |
-
"""
|
| 144 |
-
if not self._connected or not self.spreadsheet_id:
|
| 145 |
-
logger.warning("%s: Push nicht möglich - keine Verbindung oder Spreadsheet-ID.", self.name)
|
| 146 |
-
return False
|
| 147 |
-
|
| 148 |
-
try:
|
| 149 |
-
for sheet_name, rows in data.items():
|
| 150 |
-
# Überschreibt oder aktualisiert das Tabellenblatt für diese Location
|
| 151 |
-
self.gateway.write_values(
|
| 152 |
-
self.spreadsheet_id,
|
| 153 |
-
range_name=f"'{sheet_name}'!A1",
|
| 154 |
-
values=rows
|
| 155 |
-
)
|
| 156 |
-
logger.debug("Daten für Location '%s' erfolgreich gepusht.", sheet_name)
|
| 157 |
-
|
| 158 |
-
return True
|
| 159 |
-
|
| 160 |
-
except Exception as error:
|
| 161 |
-
logger.exception("Fehler beim Push der Google Sheets Daten: %s", error)
|
| 162 |
-
return False
|
| 163 |
-
|
| 164 |
-
def sync(self) -> bool:
|
| 165 |
-
"""
|
| 166 |
-
Führt einen lokalen Test- oder Direktsync durch (Pull -> Push).
|
| 167 |
-
"""
|
| 168 |
-
logger.info("Starte Sync für %s...", self.name)
|
| 169 |
-
data = self.pull()
|
| 170 |
-
if data is None:
|
| 171 |
-
return False
|
| 172 |
-
|
| 173 |
-
# Hier könnte ein Verarbeitungs- oder Abgleichschritt stattfinden
|
| 174 |
-
|
| 175 |
-
return self.push(data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/avatar_streamer.py
CHANGED
|
@@ -3,49 +3,45 @@
|
|
| 3 |
import time
|
| 4 |
from typing import Dict, Any
|
| 5 |
|
|
|
|
| 6 |
class AvatarStreamer:
|
| 7 |
-
# Klassen-Attribute verhindern den Zustandverlust bei Neu-Instanziierung
|
| 8 |
-
_avatar_name = "Sky Meilin"
|
| 9 |
-
_status = "idle"
|
| 10 |
|
| 11 |
def __init__(self):
|
| 12 |
-
print("AvatarStreamer geladen")
|
| 13 |
-
|
| 14 |
-
@property
|
| 15 |
-
def avatar_name(self) -> str:
|
| 16 |
-
return AvatarStreamer._avatar_name
|
| 17 |
-
|
| 18 |
-
@avatar_name.setter
|
| 19 |
-
def avatar_name(self, name: str):
|
| 20 |
-
AvatarStreamer._avatar_name = name
|
| 21 |
|
| 22 |
-
|
| 23 |
-
def status(self) -> str:
|
| 24 |
-
return AvatarStreamer._status
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
AvatarStreamer._status = value
|
| 29 |
|
| 30 |
def set_avatar(self, name: str):
|
|
|
|
| 31 |
self.avatar_name = name
|
| 32 |
|
| 33 |
-
def start_stream(self)
|
|
|
|
| 34 |
self.status = "streaming"
|
|
|
|
| 35 |
return {
|
| 36 |
"status": self.status,
|
| 37 |
"avatar": self.avatar_name
|
| 38 |
}
|
| 39 |
|
| 40 |
-
def stop_stream(self)
|
|
|
|
| 41 |
self.status = "offline"
|
|
|
|
| 42 |
return {
|
| 43 |
"status": self.status,
|
| 44 |
"avatar": self.avatar_name
|
| 45 |
}
|
| 46 |
|
| 47 |
-
def send_message(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
if self.status != "streaming":
|
|
|
|
| 49 |
return {
|
| 50 |
"error": "Avatar nicht aktiv"
|
| 51 |
}
|
|
@@ -55,10 +51,15 @@ class AvatarStreamer:
|
|
| 55 |
"message": text,
|
| 56 |
"timestamp": time.time()
|
| 57 |
}
|
|
|
|
| 58 |
return payload
|
| 59 |
|
| 60 |
-
def emotion(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
return {
|
| 62 |
"avatar": self.avatar_name,
|
| 63 |
"emotion": emotion_name
|
| 64 |
-
}
|
|
|
|
| 3 |
import time
|
| 4 |
from typing import Dict, Any
|
| 5 |
|
| 6 |
+
|
| 7 |
class AvatarStreamer:
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
def __init__(self):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
+
print("AvatarStreamer geladen")
|
|
|
|
|
|
|
| 12 |
|
| 13 |
+
self.avatar_name = "Sky Meilin"
|
| 14 |
+
self.status = "idle"
|
|
|
|
| 15 |
|
| 16 |
def set_avatar(self, name: str):
|
| 17 |
+
|
| 18 |
self.avatar_name = name
|
| 19 |
|
| 20 |
+
def start_stream(self):
|
| 21 |
+
|
| 22 |
self.status = "streaming"
|
| 23 |
+
|
| 24 |
return {
|
| 25 |
"status": self.status,
|
| 26 |
"avatar": self.avatar_name
|
| 27 |
}
|
| 28 |
|
| 29 |
+
def stop_stream(self):
|
| 30 |
+
|
| 31 |
self.status = "offline"
|
| 32 |
+
|
| 33 |
return {
|
| 34 |
"status": self.status,
|
| 35 |
"avatar": self.avatar_name
|
| 36 |
}
|
| 37 |
|
| 38 |
+
def send_message(
|
| 39 |
+
self,
|
| 40 |
+
text: str
|
| 41 |
+
) -> Dict[str, Any]:
|
| 42 |
+
|
| 43 |
if self.status != "streaming":
|
| 44 |
+
|
| 45 |
return {
|
| 46 |
"error": "Avatar nicht aktiv"
|
| 47 |
}
|
|
|
|
| 51 |
"message": text,
|
| 52 |
"timestamp": time.time()
|
| 53 |
}
|
| 54 |
+
|
| 55 |
return payload
|
| 56 |
|
| 57 |
+
def emotion(
|
| 58 |
+
self,
|
| 59 |
+
emotion_name: str
|
| 60 |
+
):
|
| 61 |
+
|
| 62 |
return {
|
| 63 |
"avatar": self.avatar_name,
|
| 64 |
"emotion": emotion_name
|
| 65 |
+
}
|
modules/creator_os.py
CHANGED
|
@@ -1,18 +1,7 @@
|
|
| 1 |
"""
|
| 2 |
-
modules/
|
| 3 |
-
Creator OS — Paket-Initialisierung
|
| 4 |
-
|
| 5 |
"""
|
| 6 |
|
| 7 |
-
PACKAGE_VERSION = "
|
| 8 |
-
|
| 9 |
-
# Globale System-Eckpfeiler
|
| 10 |
-
SYSTEM_AUTHORITY = {
|
| 11 |
-
"role": "Head of Automation",
|
| 12 |
-
"company": "Agency Level Up",
|
| 13 |
-
"focus": [
|
| 14 |
-
"KI-gestützte Creator-Workflows",
|
| 15 |
-
"Automatisierte Content-Systeme",
|
| 16 |
-
"Digitale Markenarchitekturen für skalierbare Projekte"
|
| 17 |
-
]
|
| 18 |
-
}
|
|
|
|
| 1 |
"""
|
| 2 |
+
modules/__init__.py
|
| 3 |
+
Creator OS — Paket-Initialisierung
|
| 4 |
+
Macht den modules/ Ordner zu einem Python-Paket.
|
| 5 |
"""
|
| 6 |
|
| 7 |
+
PACKAGE_VERSION = "modules_v1_2026_06_02"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/crypto_engine.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
# modules/crypto_engine.py
|
| 2 |
|
| 3 |
import hashlib
|
| 4 |
-
import json
|
| 5 |
import secrets
|
| 6 |
from typing import Dict, Any
|
| 7 |
|
|
@@ -9,33 +8,49 @@ from typing import Dict, Any
|
|
| 9 |
class CryptoEngine:
|
| 10 |
|
| 11 |
def __init__(self):
|
|
|
|
| 12 |
print("CryptoEngine geladen")
|
|
|
|
| 13 |
self.algorithm = "sha256"
|
| 14 |
|
| 15 |
def hash_text(self, text: str) -> str:
|
| 16 |
-
if not text:
|
| 17 |
-
return ""
|
| 18 |
-
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
| 19 |
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
return secrets.token_hex(length)
|
| 22 |
|
| 23 |
-
def verify_hash(
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
# Sort_keys=True sorgt für eine deterministische und stabile String-Form
|
| 31 |
-
payload_string = json.dumps(payload, sort_keys=True)
|
| 32 |
payload_hash = self.hash_text(payload_string)
|
| 33 |
|
| 34 |
return {
|
| 35 |
"payload": payload,
|
| 36 |
"hash": payload_hash,
|
| 37 |
-
"algorithm": self.algorithm
|
| 38 |
}
|
| 39 |
|
| 40 |
def create_session_key(self) -> str:
|
| 41 |
-
|
|
|
|
|
|
| 1 |
# modules/crypto_engine.py
|
| 2 |
|
| 3 |
import hashlib
|
|
|
|
| 4 |
import secrets
|
| 5 |
from typing import Dict, Any
|
| 6 |
|
|
|
|
| 8 |
class CryptoEngine:
|
| 9 |
|
| 10 |
def __init__(self):
|
| 11 |
+
|
| 12 |
print("CryptoEngine geladen")
|
| 13 |
+
|
| 14 |
self.algorithm = "sha256"
|
| 15 |
|
| 16 |
def hash_text(self, text: str) -> str:
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
+
return hashlib.sha256(
|
| 19 |
+
text.encode("utf-8")
|
| 20 |
+
).hexdigest()
|
| 21 |
+
|
| 22 |
+
def generate_token(
|
| 23 |
+
self,
|
| 24 |
+
length: int = 32
|
| 25 |
+
) -> str:
|
| 26 |
+
|
| 27 |
return secrets.token_hex(length)
|
| 28 |
|
| 29 |
+
def verify_hash(
|
| 30 |
+
self,
|
| 31 |
+
text: str,
|
| 32 |
+
hashed: str
|
| 33 |
+
) -> bool:
|
| 34 |
|
| 35 |
+
new_hash = self.hash_text(text)
|
| 36 |
+
|
| 37 |
+
return new_hash == hashed
|
| 38 |
+
|
| 39 |
+
def secure_payload(
|
| 40 |
+
self,
|
| 41 |
+
payload: Dict[str, Any]
|
| 42 |
+
) -> Dict[str, Any]:
|
| 43 |
+
|
| 44 |
+
payload_string = str(payload)
|
| 45 |
|
|
|
|
|
|
|
| 46 |
payload_hash = self.hash_text(payload_string)
|
| 47 |
|
| 48 |
return {
|
| 49 |
"payload": payload,
|
| 50 |
"hash": payload_hash,
|
| 51 |
+
"algorithm": self.algorithm
|
| 52 |
}
|
| 53 |
|
| 54 |
def create_session_key(self) -> str:
|
| 55 |
+
|
| 56 |
+
return secrets.token_urlsafe(64)
|
modules/dashboard/__init__.py
DELETED
|
@@ -1,112 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
__init__.py
|
| 3 |
-
SkyOS Dashboard Module
|
| 4 |
-
|
| 5 |
-
Initialisiert das Armaturenbrett-Modul.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from __future__ import annotations
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
# ---------------------------------------------------------
|
| 15 |
-
# Modul Informationen
|
| 16 |
-
# ---------------------------------------------------------
|
| 17 |
-
|
| 18 |
-
__title__ = "SkyOS Armaturenbrett"
|
| 19 |
-
__version__ = "0.1.0"
|
| 20 |
-
__author__ = "Sky Meilin"
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
# ---------------------------------------------------------
|
| 24 |
-
# Konfiguration
|
| 25 |
-
# ---------------------------------------------------------
|
| 26 |
-
|
| 27 |
-
from .config import (
|
| 28 |
-
DASHBOARD,
|
| 29 |
-
UI,
|
| 30 |
-
BACKEND,
|
| 31 |
-
get_config,
|
| 32 |
-
)
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
# ---------------------------------------------------------
|
| 36 |
-
# Theme
|
| 37 |
-
# ---------------------------------------------------------
|
| 38 |
-
|
| 39 |
-
from .theme import (
|
| 40 |
-
DashboardTheme,
|
| 41 |
-
DEFAULT_THEME,
|
| 42 |
-
get_theme,
|
| 43 |
-
)
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
# ---------------------------------------------------------
|
| 47 |
-
# Icons
|
| 48 |
-
# ---------------------------------------------------------
|
| 49 |
-
|
| 50 |
-
from .icons import (
|
| 51 |
-
ICONS,
|
| 52 |
-
get_icon,
|
| 53 |
-
)
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
# ---------------------------------------------------------
|
| 57 |
-
# Utilities
|
| 58 |
-
# ---------------------------------------------------------
|
| 59 |
-
|
| 60 |
-
from .utils import (
|
| 61 |
-
current_time,
|
| 62 |
-
current_timestamp,
|
| 63 |
-
format_status,
|
| 64 |
-
clean_text,
|
| 65 |
-
)
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
# ---------------------------------------------------------
|
| 69 |
-
# Event Handler
|
| 70 |
-
# ---------------------------------------------------------
|
| 71 |
-
|
| 72 |
-
from .event_handler import (
|
| 73 |
-
register_events,
|
| 74 |
-
send_message,
|
| 75 |
-
refresh_status,
|
| 76 |
-
refresh_header,
|
| 77 |
-
)
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
# ---------------------------------------------------------
|
| 81 |
-
# Public API
|
| 82 |
-
# ---------------------------------------------------------
|
| 83 |
-
|
| 84 |
-
__all__ = [
|
| 85 |
-
|
| 86 |
-
# Config
|
| 87 |
-
"DASHBOARD",
|
| 88 |
-
"UI",
|
| 89 |
-
"BACKEND",
|
| 90 |
-
"get_config",
|
| 91 |
-
|
| 92 |
-
# Theme
|
| 93 |
-
"DashboardTheme",
|
| 94 |
-
"DEFAULT_THEME",
|
| 95 |
-
"get_theme",
|
| 96 |
-
|
| 97 |
-
# Icons
|
| 98 |
-
"ICONS",
|
| 99 |
-
"get_icon",
|
| 100 |
-
|
| 101 |
-
# Utils
|
| 102 |
-
"current_time",
|
| 103 |
-
"current_timestamp",
|
| 104 |
-
"format_status",
|
| 105 |
-
"clean_text",
|
| 106 |
-
|
| 107 |
-
# Events
|
| 108 |
-
"register_events",
|
| 109 |
-
"send_message",
|
| 110 |
-
"refresh_status",
|
| 111 |
-
"refresh_header",
|
| 112 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/dashboard/chat_panel.py
DELETED
|
@@ -1,41 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
chat_panel.py
|
| 3 |
-
SkyOS Chat
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
from __future__ import annotations
|
| 7 |
-
|
| 8 |
-
import gradio as gr
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
def build_chat_panel() -> dict:
|
| 12 |
-
|
| 13 |
-
with gr.Column(scale=2):
|
| 14 |
-
|
| 15 |
-
chatbot = gr.Chatbot(
|
| 16 |
-
label="SkyOS Chat",
|
| 17 |
-
type="messages",
|
| 18 |
-
)
|
| 19 |
-
|
| 20 |
-
message = gr.Textbox(
|
| 21 |
-
label="Nachricht",
|
| 22 |
-
placeholder="Nachricht eingeben...",
|
| 23 |
-
)
|
| 24 |
-
|
| 25 |
-
audio = gr.Audio(
|
| 26 |
-
sources=["microphone"],
|
| 27 |
-
type="filepath",
|
| 28 |
-
label="Sprache",
|
| 29 |
-
)
|
| 30 |
-
|
| 31 |
-
send = gr.Button(
|
| 32 |
-
"Senden",
|
| 33 |
-
variant="primary",
|
| 34 |
-
)
|
| 35 |
-
|
| 36 |
-
return {
|
| 37 |
-
"chatbot": chatbot,
|
| 38 |
-
"message": message,
|
| 39 |
-
"audio": audio,
|
| 40 |
-
"send": send,
|
| 41 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/dashboard/config.py
DELETED
|
@@ -1,97 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
config.py
|
| 3 |
-
SkyOS Dashboard Configuration
|
| 4 |
-
|
| 5 |
-
Zentrale Konfiguration für UI, Events und Backend-Verbindungen.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from __future__ import annotations
|
| 12 |
-
|
| 13 |
-
from dataclasses import dataclass
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
# ---------------------------------------------------------
|
| 17 |
-
# Dashboard Settings
|
| 18 |
-
# ---------------------------------------------------------
|
| 19 |
-
|
| 20 |
-
@dataclass(frozen=True)
|
| 21 |
-
class DashboardConfig:
|
| 22 |
-
"""
|
| 23 |
-
Globale Dashboard-Konfiguration.
|
| 24 |
-
"""
|
| 25 |
-
|
| 26 |
-
title: str = "SkyOS Dashboard"
|
| 27 |
-
version: str = "0.1.0"
|
| 28 |
-
|
| 29 |
-
refresh_interval: int = 5000 # Millisekunden
|
| 30 |
-
|
| 31 |
-
enable_audio: bool = True
|
| 32 |
-
enable_stream: bool = True
|
| 33 |
-
enable_navigation: bool = True
|
| 34 |
-
|
| 35 |
-
debug_mode: bool = False
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
# ---------------------------------------------------------
|
| 39 |
-
# UI Settings
|
| 40 |
-
# ---------------------------------------------------------
|
| 41 |
-
|
| 42 |
-
@dataclass(frozen=True)
|
| 43 |
-
class UIConfig:
|
| 44 |
-
"""
|
| 45 |
-
Layout-Konfiguration.
|
| 46 |
-
"""
|
| 47 |
-
|
| 48 |
-
sidebar_width: int = 280
|
| 49 |
-
panel_radius: int = 12
|
| 50 |
-
|
| 51 |
-
max_chat_height: int = 600
|
| 52 |
-
|
| 53 |
-
compact_mode: bool = False
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
# ---------------------------------------------------------
|
| 57 |
-
# Backend Settings
|
| 58 |
-
# ---------------------------------------------------------
|
| 59 |
-
|
| 60 |
-
@dataclass(frozen=True)
|
| 61 |
-
class BackendConfig:
|
| 62 |
-
"""
|
| 63 |
-
Verbindung zu SkyOS Backend-Modulen.
|
| 64 |
-
"""
|
| 65 |
-
|
| 66 |
-
router_enabled: bool = False
|
| 67 |
-
status_monitor_enabled: bool = False
|
| 68 |
-
|
| 69 |
-
router_name: str = "SkyOS Router"
|
| 70 |
-
creator_name: str = "CreatorOS"
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
# ---------------------------------------------------------
|
| 74 |
-
# Runtime Configuration
|
| 75 |
-
# ---------------------------------------------------------
|
| 76 |
-
|
| 77 |
-
DASHBOARD = DashboardConfig()
|
| 78 |
-
|
| 79 |
-
UI = UIConfig()
|
| 80 |
-
|
| 81 |
-
BACKEND = BackendConfig()
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
# ---------------------------------------------------------
|
| 85 |
-
# Helper
|
| 86 |
-
# ---------------------------------------------------------
|
| 87 |
-
|
| 88 |
-
def get_config():
|
| 89 |
-
"""
|
| 90 |
-
Liefert komplette Dashboard-Konfiguration.
|
| 91 |
-
"""
|
| 92 |
-
|
| 93 |
-
return {
|
| 94 |
-
"dashboard": DASHBOARD,
|
| 95 |
-
"ui": UI,
|
| 96 |
-
"backend": BACKEND,
|
| 97 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/dashboard/dashboard.py
DELETED
|
@@ -1,74 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
dashboard.py
|
| 3 |
-
SkyOS Dashboard Builder
|
| 4 |
-
|
| 5 |
-
Erstellt das komplette Gradio-Dashboard und verbindet
|
| 6 |
-
alle Dashboard-Module.
|
| 7 |
-
|
| 8 |
-
Autor: Sky Meilin
|
| 9 |
-
Projekt: SkyOS
|
| 10 |
-
"""
|
| 11 |
-
|
| 12 |
-
from __future__ import annotations
|
| 13 |
-
|
| 14 |
-
import gradio as gr
|
| 15 |
-
|
| 16 |
-
from .theme import THEME
|
| 17 |
-
from .header import build_header
|
| 18 |
-
from .status_panel import build_status_panel
|
| 19 |
-
from .stream_panel import build_stream_panel
|
| 20 |
-
from .chat_panel import build_chat_panel
|
| 21 |
-
from .navigation_panel import build_navigation_panel
|
| 22 |
-
from .event_handler import register_events
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
def create_dashboard() -> gr.Blocks:
|
| 26 |
-
"""
|
| 27 |
-
Erstellt das komplette Dashboard.
|
| 28 |
-
"""
|
| 29 |
-
|
| 30 |
-
with gr.Blocks(
|
| 31 |
-
title="SkyOS Dashboard"
|
| 32 |
-
) as demo:
|
| 33 |
-
|
| 34 |
-
# -----------------------------------------
|
| 35 |
-
# Header
|
| 36 |
-
# -----------------------------------------
|
| 37 |
-
build_header()
|
| 38 |
-
|
| 39 |
-
gr.Markdown("---")
|
| 40 |
-
|
| 41 |
-
# -----------------------------------------
|
| 42 |
-
# Status
|
| 43 |
-
# -----------------------------------------
|
| 44 |
-
status_components = build_status_panel()
|
| 45 |
-
|
| 46 |
-
gr.Markdown("---")
|
| 47 |
-
|
| 48 |
-
# -----------------------------------------
|
| 49 |
-
# Hauptbereich
|
| 50 |
-
# -----------------------------------------
|
| 51 |
-
with gr.Row():
|
| 52 |
-
|
| 53 |
-
stream_components = build_stream_panel()
|
| 54 |
-
|
| 55 |
-
chat_components = build_chat_panel()
|
| 56 |
-
|
| 57 |
-
gr.Markdown("---")
|
| 58 |
-
|
| 59 |
-
# -----------------------------------------
|
| 60 |
-
# Navigation
|
| 61 |
-
# -----------------------------------------
|
| 62 |
-
navigation_components = build_navigation_panel()
|
| 63 |
-
|
| 64 |
-
# -----------------------------------------
|
| 65 |
-
# Events verbinden
|
| 66 |
-
# -----------------------------------------
|
| 67 |
-
register_events(
|
| 68 |
-
status=status_components,
|
| 69 |
-
stream=stream_components,
|
| 70 |
-
chat=chat_components,
|
| 71 |
-
navigation=navigation_components,
|
| 72 |
-
)
|
| 73 |
-
|
| 74 |
-
return demo
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/dashboard/event_handler.py
DELETED
|
@@ -1,184 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
event_handler.py
|
| 3 |
-
SkyOS Dashboard Event Handler
|
| 4 |
-
|
| 5 |
-
Verbindet alle UI-Komponenten mit den Backend-Modulen.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from __future__ import annotations
|
| 12 |
-
|
| 13 |
-
import logging
|
| 14 |
-
|
| 15 |
-
logger = logging.getLogger("SkyOS.Dashboard")
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
# ---------------------------------------------------------
|
| 19 |
-
# Backend Bridge
|
| 20 |
-
# ---------------------------------------------------------
|
| 21 |
-
|
| 22 |
-
def process_message(message: str) -> str:
|
| 23 |
-
"""
|
| 24 |
-
Übergangspunkt zum SkyOS Router.
|
| 25 |
-
|
| 26 |
-
Wird später ersetzt durch:
|
| 27 |
-
Router.handle(message)
|
| 28 |
-
"""
|
| 29 |
-
|
| 30 |
-
return (
|
| 31 |
-
"Nachricht empfangen. "
|
| 32 |
-
"Die Verarbeitung übernimmt später der SkyOS Router."
|
| 33 |
-
)
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
# ---------------------------------------------------------
|
| 37 |
-
# Chat Events
|
| 38 |
-
# ---------------------------------------------------------
|
| 39 |
-
|
| 40 |
-
def send_message(text, audio):
|
| 41 |
-
"""
|
| 42 |
-
Standard-Chatcallback.
|
| 43 |
-
"""
|
| 44 |
-
|
| 45 |
-
try:
|
| 46 |
-
if text:
|
| 47 |
-
user_input = text
|
| 48 |
-
|
| 49 |
-
elif audio:
|
| 50 |
-
user_input = "🎤 Spracheingabe empfangen."
|
| 51 |
-
|
| 52 |
-
else:
|
| 53 |
-
return [], "", None
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
response = process_message(user_input)
|
| 57 |
-
|
| 58 |
-
history = [
|
| 59 |
-
{
|
| 60 |
-
"role": "user",
|
| 61 |
-
"content": user_input,
|
| 62 |
-
},
|
| 63 |
-
{
|
| 64 |
-
"role": "assistant",
|
| 65 |
-
"content": response,
|
| 66 |
-
},
|
| 67 |
-
]
|
| 68 |
-
|
| 69 |
-
return history, "", None
|
| 70 |
-
|
| 71 |
-
except Exception:
|
| 72 |
-
logger.exception("Chat-Verarbeitung fehlgeschlagen.")
|
| 73 |
-
|
| 74 |
-
return (
|
| 75 |
-
[
|
| 76 |
-
{
|
| 77 |
-
"role": "assistant",
|
| 78 |
-
"content": "⚠️ Fehler bei der Verarbeitung."
|
| 79 |
-
}
|
| 80 |
-
],
|
| 81 |
-
"",
|
| 82 |
-
None,
|
| 83 |
-
)
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
# ---------------------------------------------------------
|
| 87 |
-
# Status Events
|
| 88 |
-
# ---------------------------------------------------------
|
| 89 |
-
|
| 90 |
-
def refresh_status():
|
| 91 |
-
"""
|
| 92 |
-
Status-Aktualisierung.
|
| 93 |
-
|
| 94 |
-
Später:
|
| 95 |
-
StatusMonitor.get_status()
|
| 96 |
-
"""
|
| 97 |
-
|
| 98 |
-
logger.info("Status aktualisiert")
|
| 99 |
-
|
| 100 |
-
return (
|
| 101 |
-
"🟢 Online",
|
| 102 |
-
"🟡 Nicht verbunden",
|
| 103 |
-
"🟢 Aktiv",
|
| 104 |
-
"🟢 Aktiv",
|
| 105 |
-
"🟢 Aktiv",
|
| 106 |
-
"🟢 Aktiv",
|
| 107 |
-
"🟢 Online",
|
| 108 |
-
"🟢 Online",
|
| 109 |
-
)
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
# ---------------------------------------------------------
|
| 113 |
-
# Header Events
|
| 114 |
-
# ---------------------------------------------------------
|
| 115 |
-
|
| 116 |
-
def refresh_header():
|
| 117 |
-
"""
|
| 118 |
-
Aktualisiert die Dashboard-Uhr.
|
| 119 |
-
"""
|
| 120 |
-
|
| 121 |
-
from .header import current_time
|
| 122 |
-
|
| 123 |
-
return (
|
| 124 |
-
current_time(),
|
| 125 |
-
)
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
# ---------------------------------------------------------
|
| 129 |
-
# Event Registrierung
|
| 130 |
-
# ---------------------------------------------------------
|
| 131 |
-
|
| 132 |
-
def register_events(
|
| 133 |
-
*,
|
| 134 |
-
status: dict,
|
| 135 |
-
stream: dict,
|
| 136 |
-
chat: dict,
|
| 137 |
-
navigation: dict,
|
| 138 |
-
):
|
| 139 |
-
"""
|
| 140 |
-
Registriert alle Dashboard-Events.
|
| 141 |
-
"""
|
| 142 |
-
|
| 143 |
-
logger.info("Registriere Dashboard Events...")
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
# Chat Button
|
| 147 |
-
|
| 148 |
-
if "send" in chat:
|
| 149 |
-
|
| 150 |
-
chat["send"].click(
|
| 151 |
-
fn=send_message,
|
| 152 |
-
inputs=[
|
| 153 |
-
chat["message"],
|
| 154 |
-
chat["audio"],
|
| 155 |
-
],
|
| 156 |
-
outputs=[
|
| 157 |
-
chat["chatbot"],
|
| 158 |
-
chat["message"],
|
| 159 |
-
chat["audio"],
|
| 160 |
-
],
|
| 161 |
-
)
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
# Enter Taste
|
| 165 |
-
|
| 166 |
-
if "message" in chat:
|
| 167 |
-
|
| 168 |
-
chat["message"].submit(
|
| 169 |
-
fn=send_message,
|
| 170 |
-
inputs=[
|
| 171 |
-
chat["message"],
|
| 172 |
-
chat["audio"],
|
| 173 |
-
],
|
| 174 |
-
outputs=[
|
| 175 |
-
chat["chatbot"],
|
| 176 |
-
chat["message"],
|
| 177 |
-
chat["audio"],
|
| 178 |
-
],
|
| 179 |
-
)
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
logger.info(
|
| 183 |
-
"Dashboard Events registriert."
|
| 184 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/dashboard/header.py
DELETED
|
@@ -1,113 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
header.py
|
| 3 |
-
SkyOS Dashboard Header
|
| 4 |
-
|
| 5 |
-
Erstellt den Kopfbereich des Dashboards mit
|
| 6 |
-
Logo, Systeminformationen und Live-Uhr.
|
| 7 |
-
|
| 8 |
-
Autor: Sky Meilin
|
| 9 |
-
Projekt: SkyOS
|
| 10 |
-
"""
|
| 11 |
-
|
| 12 |
-
from __future__ import annotations
|
| 13 |
-
|
| 14 |
-
from datetime import datetime
|
| 15 |
-
import zoneinfo
|
| 16 |
-
|
| 17 |
-
import gradio as gr
|
| 18 |
-
|
| 19 |
-
from persona_sky_meilin import (
|
| 20 |
-
SYSTEM_NAME,
|
| 21 |
-
AVATAR_PERSONA,
|
| 22 |
-
AVATAR_JOBTITEL,
|
| 23 |
-
COMPANY_NAME,
|
| 24 |
-
)
|
| 25 |
-
|
| 26 |
-
# ---------------------------------------------------------
|
| 27 |
-
# Zeitzone
|
| 28 |
-
# ---------------------------------------------------------
|
| 29 |
-
|
| 30 |
-
BERLIN_TZ = zoneinfo.ZoneInfo("Europe/Berlin")
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
# ---------------------------------------------------------
|
| 34 |
-
# Hilfsfunktionen
|
| 35 |
-
# ---------------------------------------------------------
|
| 36 |
-
|
| 37 |
-
def current_time() -> str:
|
| 38 |
-
"""Aktuelle Uhrzeit in Deutschland."""
|
| 39 |
-
|
| 40 |
-
return datetime.now(BERLIN_TZ).strftime("%d.%m.%Y • %H:%M:%S")
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
# ---------------------------------------------------------
|
| 44 |
-
# Header
|
| 45 |
-
# ---------------------------------------------------------
|
| 46 |
-
|
| 47 |
-
def build_header() -> dict:
|
| 48 |
-
"""
|
| 49 |
-
Erstellt den Dashboard-Header.
|
| 50 |
-
|
| 51 |
-
Returns
|
| 52 |
-
-------
|
| 53 |
-
dict
|
| 54 |
-
Referenzen auf Komponenten.
|
| 55 |
-
"""
|
| 56 |
-
|
| 57 |
-
with gr.Column():
|
| 58 |
-
|
| 59 |
-
gr.Markdown(
|
| 60 |
-
f"""
|
| 61 |
-
# 🌌 {SYSTEM_NAME}
|
| 62 |
-
|
| 63 |
-
### 👤 {AVATAR_PERSONA}
|
| 64 |
-
**{AVATAR_JOBTITEL}**
|
| 65 |
-
|
| 66 |
-
🏢 {COMPANY_NAME}
|
| 67 |
-
"""
|
| 68 |
-
)
|
| 69 |
-
|
| 70 |
-
with gr.Row():
|
| 71 |
-
|
| 72 |
-
clock = gr.Textbox(
|
| 73 |
-
value=current_time(),
|
| 74 |
-
label="🕒 Uhrzeit",
|
| 75 |
-
interactive=False,
|
| 76 |
-
scale=1,
|
| 77 |
-
)
|
| 78 |
-
|
| 79 |
-
system = gr.Textbox(
|
| 80 |
-
value=SYSTEM_NAME,
|
| 81 |
-
label="💻 System",
|
| 82 |
-
interactive=False,
|
| 83 |
-
scale=1,
|
| 84 |
-
)
|
| 85 |
-
|
| 86 |
-
company = gr.Textbox(
|
| 87 |
-
value=COMPANY_NAME,
|
| 88 |
-
label="🏢 Unternehmen",
|
| 89 |
-
interactive=False,
|
| 90 |
-
scale=1,
|
| 91 |
-
)
|
| 92 |
-
|
| 93 |
-
return {
|
| 94 |
-
"clock": clock,
|
| 95 |
-
"system": system,
|
| 96 |
-
"company": company,
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
# ---------------------------------------------------------
|
| 101 |
-
# Live-Update
|
| 102 |
-
# ---------------------------------------------------------
|
| 103 |
-
|
| 104 |
-
def refresh_header():
|
| 105 |
-
"""
|
| 106 |
-
Aktualisiert die dynamischen Header-Daten.
|
| 107 |
-
"""
|
| 108 |
-
|
| 109 |
-
return {
|
| 110 |
-
"clock": current_time(),
|
| 111 |
-
"system": SYSTEM_NAME,
|
| 112 |
-
"company": COMPANY_NAME,
|
| 113 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/dashboard/icons.py
DELETED
|
@@ -1,114 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
icons.py
|
| 3 |
-
SkyOS Dashboard Icons
|
| 4 |
-
|
| 5 |
-
Zentrale Icon-Verwaltung für das Armaturenbrett.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from __future__ import annotations
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
# ---------------------------------------------------------
|
| 15 |
-
# System Icons
|
| 16 |
-
# ---------------------------------------------------------
|
| 17 |
-
|
| 18 |
-
ONLINE = "🟢"
|
| 19 |
-
OFFLINE = "⚫"
|
| 20 |
-
WARNING = "🟡"
|
| 21 |
-
ERROR = "🔴"
|
| 22 |
-
ACTIVE = "⚡"
|
| 23 |
-
INACTIVE = "○"
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
# ---------------------------------------------------------
|
| 27 |
-
# Navigation Icons
|
| 28 |
-
# ---------------------------------------------------------
|
| 29 |
-
|
| 30 |
-
HOME = "🏠"
|
| 31 |
-
DASHBOARD = "📊"
|
| 32 |
-
SYSTEM = "⚙️"
|
| 33 |
-
SETTINGS = "🔧"
|
| 34 |
-
LOGS = "📜"
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
# ---------------------------------------------------------
|
| 38 |
-
# Chat Icons
|
| 39 |
-
# ---------------------------------------------------------
|
| 40 |
-
|
| 41 |
-
CHAT = "💬"
|
| 42 |
-
SEND = "📤"
|
| 43 |
-
MICROPHONE = "🎤"
|
| 44 |
-
USER = "👤"
|
| 45 |
-
ASSISTANT = "🤖"
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
# ---------------------------------------------------------
|
| 49 |
-
# Stream Icons
|
| 50 |
-
# ---------------------------------------------------------
|
| 51 |
-
|
| 52 |
-
STREAM = "📡"
|
| 53 |
-
VIDEO = "🎥"
|
| 54 |
-
AUDIO = "🔊"
|
| 55 |
-
LIVE = "🔴"
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
# ---------------------------------------------------------
|
| 59 |
-
# Status Icons
|
| 60 |
-
# ---------------------------------------------------------
|
| 61 |
-
|
| 62 |
-
CPU = "🖥️"
|
| 63 |
-
MEMORY = "💾"
|
| 64 |
-
NETWORK = "🌐"
|
| 65 |
-
DATABASE = "🗄️"
|
| 66 |
-
SECURITY = "🛡️"
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
# ---------------------------------------------------------
|
| 70 |
-
# Actions
|
| 71 |
-
# ---------------------------------------------------------
|
| 72 |
-
|
| 73 |
-
REFRESH = "🔄"
|
| 74 |
-
SEARCH = "🔍"
|
| 75 |
-
SAVE = "💾"
|
| 76 |
-
CLOSE = "✖"
|
| 77 |
-
CHECK = "✔"
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
# ---------------------------------------------------------
|
| 81 |
-
# Icon Sammlung
|
| 82 |
-
# ---------------------------------------------------------
|
| 83 |
-
|
| 84 |
-
ICONS = {
|
| 85 |
-
"online": ONLINE,
|
| 86 |
-
"offline": OFFLINE,
|
| 87 |
-
"warning": WARNING,
|
| 88 |
-
"error": ERROR,
|
| 89 |
-
|
| 90 |
-
"home": HOME,
|
| 91 |
-
"dashboard": DASHBOARD,
|
| 92 |
-
"settings": SETTINGS,
|
| 93 |
-
|
| 94 |
-
"chat": CHAT,
|
| 95 |
-
"send": SEND,
|
| 96 |
-
"microphone": MICROPHONE,
|
| 97 |
-
|
| 98 |
-
"stream": STREAM,
|
| 99 |
-
"live": LIVE,
|
| 100 |
-
|
| 101 |
-
"refresh": REFRESH,
|
| 102 |
-
"check": CHECK,
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
def get_icon(name: str) -> str:
|
| 107 |
-
"""
|
| 108 |
-
Liefert ein Icon anhand des Namens.
|
| 109 |
-
"""
|
| 110 |
-
|
| 111 |
-
return ICONS.get(
|
| 112 |
-
name,
|
| 113 |
-
"?"
|
| 114 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/dashboard/navigation_panel.py
DELETED
|
@@ -1,53 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
navigation_panel.py
|
| 3 |
-
Navigation
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
from __future__ import annotations
|
| 7 |
-
|
| 8 |
-
import gradio as gr
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
BUTTON_STYLE = """
|
| 12 |
-
width:100%;
|
| 13 |
-
padding:15px;
|
| 14 |
-
background:#f3f4f6;
|
| 15 |
-
border:1px solid #d1d5db;
|
| 16 |
-
border-radius:8px;
|
| 17 |
-
cursor:pointer;
|
| 18 |
-
"""
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
def build_navigation_panel():
|
| 22 |
-
|
| 23 |
-
gr.Markdown("## 🏢 Navigation")
|
| 24 |
-
|
| 25 |
-
with gr.Row():
|
| 26 |
-
|
| 27 |
-
gr.HTML(
|
| 28 |
-
f'<a href="/management" target="_blank"><button style="{BUTTON_STYLE}">💼 Management</button></a>'
|
| 29 |
-
)
|
| 30 |
-
|
| 31 |
-
gr.HTML(
|
| 32 |
-
f'<a href="/client" target="_blank"><button style="{BUTTON_STYLE}">🤝 Client</button></a>'
|
| 33 |
-
)
|
| 34 |
-
|
| 35 |
-
gr.HTML(
|
| 36 |
-
f'<a href="/creation" target="_blank"><button style="{BUTTON_STYLE}">🎨 Creation</button></a>'
|
| 37 |
-
)
|
| 38 |
-
|
| 39 |
-
with gr.Row():
|
| 40 |
-
|
| 41 |
-
gr.HTML(
|
| 42 |
-
f'<a href="/technical" target="_blank"><button style="{BUTTON_STYLE}">📊 Technical</button></a>'
|
| 43 |
-
)
|
| 44 |
-
|
| 45 |
-
gr.HTML(
|
| 46 |
-
f'<a href="/data" target="_blank"><button style="{BUTTON_STYLE}">⚡ Data</button></a>'
|
| 47 |
-
)
|
| 48 |
-
|
| 49 |
-
gr.HTML(
|
| 50 |
-
f'<a href="/settings" target="_blank"><button style="{BUTTON_STYLE}">⚙️ Einstellungen</button></a>'
|
| 51 |
-
)
|
| 52 |
-
|
| 53 |
-
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/dashboard/status_panel.py
DELETED
|
@@ -1,78 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
status_panel.py
|
| 3 |
-
SkyOS Dashboard
|
| 4 |
-
|
| 5 |
-
Systemstatus-Panel.
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
from __future__ import annotations
|
| 9 |
-
|
| 10 |
-
import gradio as gr
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
def build_status_panel() -> dict:
|
| 14 |
-
|
| 15 |
-
gr.Markdown("## 📊 Systemstatus")
|
| 16 |
-
|
| 17 |
-
with gr.Row():
|
| 18 |
-
|
| 19 |
-
avatar = gr.Textbox(
|
| 20 |
-
value="🟢 Online",
|
| 21 |
-
label="Avatar",
|
| 22 |
-
interactive=False,
|
| 23 |
-
)
|
| 24 |
-
|
| 25 |
-
google = gr.Textbox(
|
| 26 |
-
value="🟡 Nicht verbunden",
|
| 27 |
-
label="Google",
|
| 28 |
-
interactive=False,
|
| 29 |
-
)
|
| 30 |
-
|
| 31 |
-
router = gr.Textbox(
|
| 32 |
-
value="🟢 Aktiv",
|
| 33 |
-
label="Router",
|
| 34 |
-
interactive=False,
|
| 35 |
-
)
|
| 36 |
-
|
| 37 |
-
crm = gr.Textbox(
|
| 38 |
-
value="🟢 Aktiv",
|
| 39 |
-
label="CRM",
|
| 40 |
-
interactive=False,
|
| 41 |
-
)
|
| 42 |
-
|
| 43 |
-
with gr.Row():
|
| 44 |
-
|
| 45 |
-
creator = gr.Textbox(
|
| 46 |
-
value="🟢 Aktiv",
|
| 47 |
-
label="Creator",
|
| 48 |
-
interactive=False,
|
| 49 |
-
)
|
| 50 |
-
|
| 51 |
-
worklog = gr.Textbox(
|
| 52 |
-
value="🟢 Aktiv",
|
| 53 |
-
label="Worklog",
|
| 54 |
-
interactive=False,
|
| 55 |
-
)
|
| 56 |
-
|
| 57 |
-
github = gr.Textbox(
|
| 58 |
-
value="🟢 Online",
|
| 59 |
-
label="GitHub",
|
| 60 |
-
interactive=False,
|
| 61 |
-
)
|
| 62 |
-
|
| 63 |
-
huggingface = gr.Textbox(
|
| 64 |
-
value="🟢 Online",
|
| 65 |
-
label="Hugging Face",
|
| 66 |
-
interactive=False,
|
| 67 |
-
)
|
| 68 |
-
|
| 69 |
-
return {
|
| 70 |
-
"avatar": avatar,
|
| 71 |
-
"google": google,
|
| 72 |
-
"router": router,
|
| 73 |
-
"crm": crm,
|
| 74 |
-
"creator": creator,
|
| 75 |
-
"worklog": worklog,
|
| 76 |
-
"github": github,
|
| 77 |
-
"huggingface": huggingface,
|
| 78 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/dashboard/theme.py
DELETED
|
@@ -1,66 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
theme.py
|
| 3 |
-
SkyOS Dashboard Theme
|
| 4 |
-
|
| 5 |
-
Zentrale Darstellungskonfiguration für das Armaturenbrett.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from __future__ import annotations
|
| 12 |
-
|
| 13 |
-
from dataclasses import dataclass
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
# ---------------------------------------------------------
|
| 17 |
-
# Theme Definition
|
| 18 |
-
# ---------------------------------------------------------
|
| 19 |
-
|
| 20 |
-
@dataclass(frozen=True)
|
| 21 |
-
class DashboardTheme:
|
| 22 |
-
"""
|
| 23 |
-
Farb- und Style-Konfiguration des Dashboards.
|
| 24 |
-
"""
|
| 25 |
-
|
| 26 |
-
# Hintergrund
|
| 27 |
-
background: str = "#0b1120"
|
| 28 |
-
surface: str = "#111827"
|
| 29 |
-
panel: str = "#1f2937"
|
| 30 |
-
|
| 31 |
-
# Text
|
| 32 |
-
text: str = "#f8fafc"
|
| 33 |
-
muted: str = "#94a3b8"
|
| 34 |
-
|
| 35 |
-
# Statusfarben
|
| 36 |
-
online: str = "#22c55e"
|
| 37 |
-
warning: str = "#eab308"
|
| 38 |
-
error: str = "#ef4444"
|
| 39 |
-
offline: str = "#64748b"
|
| 40 |
-
|
| 41 |
-
# Akzente
|
| 42 |
-
primary: str = "#38bdf8"
|
| 43 |
-
secondary: str = "#6366f1"
|
| 44 |
-
|
| 45 |
-
# UI
|
| 46 |
-
border_radius: str = "12px"
|
| 47 |
-
panel_padding: str = "16px"
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
# ---------------------------------------------------------
|
| 51 |
-
# Standard Theme
|
| 52 |
-
# ---------------------------------------------------------
|
| 53 |
-
|
| 54 |
-
DEFAULT_THEME = DashboardTheme()
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
# ---------------------------------------------------------
|
| 58 |
-
# Theme Zugriff
|
| 59 |
-
# ---------------------------------------------------------
|
| 60 |
-
|
| 61 |
-
def get_theme() -> DashboardTheme:
|
| 62 |
-
"""
|
| 63 |
-
Gibt das aktive Dashboard Theme zurück.
|
| 64 |
-
"""
|
| 65 |
-
|
| 66 |
-
return DEFAULT_THEME
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/dashboard/utils.py
DELETED
|
@@ -1,91 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
utils.py
|
| 3 |
-
SkyOS Dashboard Utilities
|
| 4 |
-
|
| 5 |
-
Gemeinsame Hilfsfunktionen für das Armaturenbrett.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from __future__ import annotations
|
| 12 |
-
|
| 13 |
-
from datetime import datetime
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
# ---------------------------------------------------------
|
| 17 |
-
# Zeitfunktionen
|
| 18 |
-
# ---------------------------------------------------------
|
| 19 |
-
|
| 20 |
-
def current_timestamp() -> str:
|
| 21 |
-
"""
|
| 22 |
-
Erstellt einen formatierten Zeitstempel.
|
| 23 |
-
"""
|
| 24 |
-
|
| 25 |
-
return datetime.now().strftime(
|
| 26 |
-
"%d.%m.%Y %H:%M:%S"
|
| 27 |
-
)
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
def current_time() -> str:
|
| 31 |
-
"""
|
| 32 |
-
Liefert aktuelle Uhrzeit.
|
| 33 |
-
"""
|
| 34 |
-
|
| 35 |
-
return datetime.now().strftime(
|
| 36 |
-
"%H:%M:%S"
|
| 37 |
-
)
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
# ---------------------------------------------------------
|
| 41 |
-
# Status Helfer
|
| 42 |
-
# ---------------------------------------------------------
|
| 43 |
-
|
| 44 |
-
def status_icon(active: bool) -> str:
|
| 45 |
-
"""
|
| 46 |
-
Gibt Statussymbol zurück.
|
| 47 |
-
"""
|
| 48 |
-
|
| 49 |
-
return "🟢" if active else "🔴"
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
def format_status(
|
| 53 |
-
name: str,
|
| 54 |
-
active: bool,
|
| 55 |
-
) -> str:
|
| 56 |
-
"""
|
| 57 |
-
Erstellt eine Statusanzeige.
|
| 58 |
-
"""
|
| 59 |
-
|
| 60 |
-
return (
|
| 61 |
-
f"{status_icon(active)} {name}"
|
| 62 |
-
)
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
# ---------------------------------------------------------
|
| 66 |
-
# Text Helfer
|
| 67 |
-
# ---------------------------------------------------------
|
| 68 |
-
|
| 69 |
-
def clean_text(text: str | None) -> str:
|
| 70 |
-
"""
|
| 71 |
-
Bereinigt Benutzereingaben.
|
| 72 |
-
"""
|
| 73 |
-
|
| 74 |
-
if not text:
|
| 75 |
-
return ""
|
| 76 |
-
|
| 77 |
-
return text.strip()
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
def limit_text(
|
| 81 |
-
text: str,
|
| 82 |
-
length: int = 100,
|
| 83 |
-
) -> str:
|
| 84 |
-
"""
|
| 85 |
-
Kürzt lange Texte für UI-Anzeigen.
|
| 86 |
-
"""
|
| 87 |
-
|
| 88 |
-
if len(text) <= length:
|
| 89 |
-
return text
|
| 90 |
-
|
| 91 |
-
return text[:length] + "..."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/fanvue_guide.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
"""
|
| 2 |
-
modules/
|
| 3 |
-
Creator OS —
|
| 4 |
-
|
| 5 |
"""
|
| 6 |
|
| 7 |
-
PACKAGE_VERSION = "
|
|
|
|
| 1 |
"""
|
| 2 |
+
modules/__init__.py
|
| 3 |
+
Creator OS — Paket-Initialisierung
|
| 4 |
+
Macht den modules/ Ordner zu einem Python-Paket.
|
| 5 |
"""
|
| 6 |
|
| 7 |
+
PACKAGE_VERSION = "modules_v1_2026_06_02"
|
modules/google/__init__.py
DELETED
|
@@ -1,42 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
SkyOS Google Integration Package
|
| 3 |
-
================================
|
| 4 |
-
Zentrale Exporte aller Google-Module.
|
| 5 |
-
|
| 6 |
-
Autor: Sky Meilin
|
| 7 |
-
Projekt: SkyOS FullCore
|
| 8 |
-
"""
|
| 9 |
-
|
| 10 |
-
from .config import GoogleConfig
|
| 11 |
-
from .auth import GoogleAuth
|
| 12 |
-
from .oauth import OAuthManager
|
| 13 |
-
|
| 14 |
-
from .calendar_gateway import CalendarGateway
|
| 15 |
-
from .sheets_gateway import SheetsGateway
|
| 16 |
-
from .docs_gateway import DocsGateway
|
| 17 |
-
from .drive_gateway import DriveGateway
|
| 18 |
-
from .gmail_gateway import GmailGateway
|
| 19 |
-
|
| 20 |
-
from .discovery import GoogleDiscovery
|
| 21 |
-
from .utils import (
|
| 22 |
-
logger,
|
| 23 |
-
ensure_directory,
|
| 24 |
-
timestamp,
|
| 25 |
-
safe_execute
|
| 26 |
-
)
|
| 27 |
-
|
| 28 |
-
__all__ = [
|
| 29 |
-
"GoogleConfig",
|
| 30 |
-
"GoogleAuth",
|
| 31 |
-
"OAuthManager",
|
| 32 |
-
"CalendarGateway",
|
| 33 |
-
"SheetsGateway",
|
| 34 |
-
"DocsGateway",
|
| 35 |
-
"DriveGateway",
|
| 36 |
-
"GmailGateway",
|
| 37 |
-
"GoogleDiscovery",
|
| 38 |
-
"logger",
|
| 39 |
-
"ensure_directory",
|
| 40 |
-
"timestamp",
|
| 41 |
-
"safe_execute",
|
| 42 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/google/auth.py
DELETED
|
@@ -1,126 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
auth.py
|
| 3 |
-
SkyOS Google Authentication
|
| 4 |
-
|
| 5 |
-
Stellt authentifizierte Google Services für alle Module bereit.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS Full Core
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from __future__ import annotations
|
| 12 |
-
|
| 13 |
-
import logging
|
| 14 |
-
|
| 15 |
-
from googleapiclient.discovery import build
|
| 16 |
-
|
| 17 |
-
from Module.google.oauth import oauth
|
| 18 |
-
|
| 19 |
-
logger = logging.getLogger("SkyOS.GoogleAuth")
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
class GoogleAuth:
|
| 23 |
-
"""Zentrale Google-Authentifizierung."""
|
| 24 |
-
|
| 25 |
-
_credentials = None
|
| 26 |
-
_services = {}
|
| 27 |
-
|
| 28 |
-
@classmethod
|
| 29 |
-
def credentials(cls):
|
| 30 |
-
"""Liefert gültige OAuth-Credentials."""
|
| 31 |
-
|
| 32 |
-
if cls._credentials is None:
|
| 33 |
-
cls._credentials = oauth.authenticate()
|
| 34 |
-
|
| 35 |
-
return cls._credentials
|
| 36 |
-
|
| 37 |
-
@classmethod
|
| 38 |
-
def service(
|
| 39 |
-
cls,
|
| 40 |
-
api_name: str,
|
| 41 |
-
api_version: str,
|
| 42 |
-
):
|
| 43 |
-
"""
|
| 44 |
-
Erzeugt oder liefert einen zwischengespeicherten Google-Service.
|
| 45 |
-
|
| 46 |
-
Beispiel:
|
| 47 |
-
GoogleAuth.service("drive", "v3")
|
| 48 |
-
"""
|
| 49 |
-
|
| 50 |
-
key = f"{api_name}:{api_version}"
|
| 51 |
-
|
| 52 |
-
if key not in cls._services:
|
| 53 |
-
|
| 54 |
-
logger.info(
|
| 55 |
-
"Initialisiere Google Service %s (%s)",
|
| 56 |
-
api_name,
|
| 57 |
-
api_version,
|
| 58 |
-
)
|
| 59 |
-
|
| 60 |
-
cls._services[key] = build(
|
| 61 |
-
api_name,
|
| 62 |
-
api_version,
|
| 63 |
-
credentials=cls.credentials(),
|
| 64 |
-
cache_discovery=False,
|
| 65 |
-
)
|
| 66 |
-
|
| 67 |
-
return cls._services[key]
|
| 68 |
-
|
| 69 |
-
@classmethod
|
| 70 |
-
def reset(cls):
|
| 71 |
-
"""Setzt alle gecachten Services zurück."""
|
| 72 |
-
|
| 73 |
-
cls._services.clear()
|
| 74 |
-
cls._credentials = None
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
# -------------------------------------------------
|
| 78 |
-
# Komfortfunktionen
|
| 79 |
-
# -------------------------------------------------
|
| 80 |
-
|
| 81 |
-
def get_credentials():
|
| 82 |
-
"""Liefert gültige OAuth-Credentials."""
|
| 83 |
-
return GoogleAuth.credentials()
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
def get_service(
|
| 87 |
-
api_name: str,
|
| 88 |
-
api_version: str,
|
| 89 |
-
):
|
| 90 |
-
"""
|
| 91 |
-
Liefert einen Google API Service.
|
| 92 |
-
|
| 93 |
-
Beispiele:
|
| 94 |
-
get_service("drive", "v3")
|
| 95 |
-
get_service("calendar", "v3")
|
| 96 |
-
get_service("gmail", "v1")
|
| 97 |
-
"""
|
| 98 |
-
|
| 99 |
-
return GoogleAuth.service(
|
| 100 |
-
api_name,
|
| 101 |
-
api_version,
|
| 102 |
-
)
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
# -------------------------------------------------
|
| 106 |
-
# Spezielle Kurzfunktionen
|
| 107 |
-
# -------------------------------------------------
|
| 108 |
-
|
| 109 |
-
def drive():
|
| 110 |
-
return get_service("drive", "v3")
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
def docs():
|
| 114 |
-
return get_service("docs", "v1")
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
def sheets():
|
| 118 |
-
return get_service("sheets", "v4")
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
def gmail():
|
| 122 |
-
return get_service("gmail", "v1")
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
def calendar():
|
| 126 |
-
return get_service("calendar", "v3")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/google/calendar_gateway.py
DELETED
|
@@ -1,166 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
calendar_gateway.py
|
| 3 |
-
SkyOS Google Calendar Gateway
|
| 4 |
-
|
| 5 |
-
Zentrale Schnittstelle zur Google Calendar API.
|
| 6 |
-
Alle Kalenderfunktionen laufen über dieses Gateway.
|
| 7 |
-
|
| 8 |
-
Autor: Sky Meilin
|
| 9 |
-
Projekt: SkyOS Full Core
|
| 10 |
-
"""
|
| 11 |
-
|
| 12 |
-
from __future__ import annotations
|
| 13 |
-
|
| 14 |
-
import logging
|
| 15 |
-
from datetime import datetime, timedelta
|
| 16 |
-
from typing import Any
|
| 17 |
-
|
| 18 |
-
from googleapiclient.discovery import build
|
| 19 |
-
|
| 20 |
-
# Gemeinsame Authentifizierung
|
| 21 |
-
from Module.google.auth import get_credentials
|
| 22 |
-
|
| 23 |
-
logger = logging.getLogger("SkyOS.Calendar")
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
class CalendarGateway:
|
| 27 |
-
"""Google Calendar Gateway"""
|
| 28 |
-
|
| 29 |
-
def __init__(self):
|
| 30 |
-
credentials = get_credentials()
|
| 31 |
-
self.service = build(
|
| 32 |
-
"calendar",
|
| 33 |
-
"v3",
|
| 34 |
-
credentials=credentials,
|
| 35 |
-
cache_discovery=False,
|
| 36 |
-
)
|
| 37 |
-
|
| 38 |
-
# -------------------------------------------------
|
| 39 |
-
# Kalender
|
| 40 |
-
# -------------------------------------------------
|
| 41 |
-
|
| 42 |
-
def list_calendars(self) -> list[dict]:
|
| 43 |
-
"""Alle verfügbaren Kalender"""
|
| 44 |
-
|
| 45 |
-
response = self.service.calendarList().list().execute()
|
| 46 |
-
return response.get("items", [])
|
| 47 |
-
|
| 48 |
-
# -------------------------------------------------
|
| 49 |
-
# Termine
|
| 50 |
-
# -------------------------------------------------
|
| 51 |
-
|
| 52 |
-
def list_events(
|
| 53 |
-
self,
|
| 54 |
-
calendar_id: str = "primary",
|
| 55 |
-
max_results: int = 20,
|
| 56 |
-
) -> list[dict]:
|
| 57 |
-
|
| 58 |
-
now = datetime.utcnow().isoformat() + "Z"
|
| 59 |
-
|
| 60 |
-
response = (
|
| 61 |
-
self.service.events()
|
| 62 |
-
.list(
|
| 63 |
-
calendarId=calendar_id,
|
| 64 |
-
timeMin=now,
|
| 65 |
-
maxResults=max_results,
|
| 66 |
-
singleEvents=True,
|
| 67 |
-
orderBy="startTime",
|
| 68 |
-
)
|
| 69 |
-
.execute()
|
| 70 |
-
)
|
| 71 |
-
|
| 72 |
-
return response.get("items", [])
|
| 73 |
-
|
| 74 |
-
def create_event(
|
| 75 |
-
self,
|
| 76 |
-
summary: str,
|
| 77 |
-
start: datetime,
|
| 78 |
-
end: datetime,
|
| 79 |
-
description: str = "",
|
| 80 |
-
location: str = "",
|
| 81 |
-
calendar_id: str = "primary",
|
| 82 |
-
) -> dict:
|
| 83 |
-
|
| 84 |
-
event = {
|
| 85 |
-
"summary": summary,
|
| 86 |
-
"description": description,
|
| 87 |
-
"location": location,
|
| 88 |
-
"start": {
|
| 89 |
-
"dateTime": start.isoformat(),
|
| 90 |
-
"timeZone": "Europe/Berlin",
|
| 91 |
-
},
|
| 92 |
-
"end": {
|
| 93 |
-
"dateTime": end.isoformat(),
|
| 94 |
-
"timeZone": "Europe/Berlin",
|
| 95 |
-
},
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
return (
|
| 99 |
-
self.service.events()
|
| 100 |
-
.insert(calendarId=calendar_id, body=event)
|
| 101 |
-
.execute()
|
| 102 |
-
)
|
| 103 |
-
|
| 104 |
-
def delete_event(
|
| 105 |
-
self,
|
| 106 |
-
event_id: str,
|
| 107 |
-
calendar_id: str = "primary",
|
| 108 |
-
):
|
| 109 |
-
|
| 110 |
-
self.service.events().delete(
|
| 111 |
-
calendarId=calendar_id,
|
| 112 |
-
eventId=event_id,
|
| 113 |
-
).execute()
|
| 114 |
-
|
| 115 |
-
def update_event(
|
| 116 |
-
self,
|
| 117 |
-
event_id: str,
|
| 118 |
-
body: dict,
|
| 119 |
-
calendar_id: str = "primary",
|
| 120 |
-
):
|
| 121 |
-
|
| 122 |
-
return (
|
| 123 |
-
self.service.events()
|
| 124 |
-
.update(
|
| 125 |
-
calendarId=calendar_id,
|
| 126 |
-
eventId=event_id,
|
| 127 |
-
body=body,
|
| 128 |
-
)
|
| 129 |
-
.execute()
|
| 130 |
-
)
|
| 131 |
-
|
| 132 |
-
def get_event(
|
| 133 |
-
self,
|
| 134 |
-
event_id: str,
|
| 135 |
-
calendar_id: str = "primary",
|
| 136 |
-
):
|
| 137 |
-
|
| 138 |
-
return (
|
| 139 |
-
self.service.events()
|
| 140 |
-
.get(
|
| 141 |
-
calendarId=calendar_id,
|
| 142 |
-
eventId=event_id,
|
| 143 |
-
)
|
| 144 |
-
.execute()
|
| 145 |
-
)
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
# -----------------------------------------------------
|
| 149 |
-
# Test
|
| 150 |
-
# -----------------------------------------------------
|
| 151 |
-
|
| 152 |
-
if __name__ == "__main__":
|
| 153 |
-
|
| 154 |
-
calendar = CalendarGateway()
|
| 155 |
-
|
| 156 |
-
print("Kalender:")
|
| 157 |
-
for cal in calendar.list_calendars():
|
| 158 |
-
print("-", cal["summary"])
|
| 159 |
-
|
| 160 |
-
print("\nNächste Termine:")
|
| 161 |
-
|
| 162 |
-
for event in calendar.list_events():
|
| 163 |
-
print(
|
| 164 |
-
event.get("start", {}).get("dateTime"),
|
| 165 |
-
event.get("summary"),
|
| 166 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/google/config.py
DELETED
|
@@ -1,121 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
config.py
|
| 3 |
-
SkyOS Google Configuration
|
| 4 |
-
|
| 5 |
-
Zentrale Konfiguration für alle Google-Dienste.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS Full Core
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from __future__ import annotations
|
| 12 |
-
|
| 13 |
-
import os
|
| 14 |
-
|
| 15 |
-
try:
|
| 16 |
-
from dotenv import load_dotenv
|
| 17 |
-
load_dotenv()
|
| 18 |
-
except ImportError:
|
| 19 |
-
pass
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
# -------------------------------------------------
|
| 23 |
-
# Projekt
|
| 24 |
-
# -------------------------------------------------
|
| 25 |
-
|
| 26 |
-
PROJECT_NAME = "SkyOS Full Core"
|
| 27 |
-
|
| 28 |
-
TIMEZONE = os.getenv("SKYOS_TIMEZONE", "Europe/Berlin")
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
# -------------------------------------------------
|
| 32 |
-
# Google OAuth
|
| 33 |
-
# -------------------------------------------------
|
| 34 |
-
|
| 35 |
-
GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID", "")
|
| 36 |
-
|
| 37 |
-
GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET", "")
|
| 38 |
-
|
| 39 |
-
GOOGLE_PROJECT_ID = os.getenv("GOOGLE_PROJECT_ID", "")
|
| 40 |
-
|
| 41 |
-
GOOGLE_REDIRECT_URI = os.getenv(
|
| 42 |
-
"GOOGLE_REDIRECT_URI",
|
| 43 |
-
"http://localhost:8080/"
|
| 44 |
-
)
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
# -------------------------------------------------
|
| 48 |
-
# Token
|
| 49 |
-
# -------------------------------------------------
|
| 50 |
-
|
| 51 |
-
TOKEN_FILE = os.getenv(
|
| 52 |
-
"GOOGLE_TOKEN_FILE",
|
| 53 |
-
"token.json"
|
| 54 |
-
)
|
| 55 |
-
|
| 56 |
-
CREDENTIALS_FILE = os.getenv(
|
| 57 |
-
"GOOGLE_CREDENTIALS_FILE",
|
| 58 |
-
"credentials.json"
|
| 59 |
-
)
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
# -------------------------------------------------
|
| 63 |
-
# OAuth Scopes
|
| 64 |
-
# -------------------------------------------------
|
| 65 |
-
|
| 66 |
-
GOOGLE_SCOPES = [
|
| 67 |
-
|
| 68 |
-
# Google Drive
|
| 69 |
-
"https://www.googleapis.com/auth/drive",
|
| 70 |
-
|
| 71 |
-
# Google Docs
|
| 72 |
-
"https://www.googleapis.com/auth/documents",
|
| 73 |
-
|
| 74 |
-
# Google Sheets
|
| 75 |
-
"https://www.googleapis.com/auth/spreadsheets",
|
| 76 |
-
|
| 77 |
-
# Gmail
|
| 78 |
-
"https://www.googleapis.com/auth/gmail.modify",
|
| 79 |
-
|
| 80 |
-
# Google Kalender
|
| 81 |
-
"https://www.googleapis.com/auth/calendar",
|
| 82 |
-
|
| 83 |
-
# Benutzerinformationen
|
| 84 |
-
"openid",
|
| 85 |
-
"https://www.googleapis.com/auth/userinfo.email",
|
| 86 |
-
"https://www.googleapis.com/auth/userinfo.profile",
|
| 87 |
-
]
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
# -------------------------------------------------
|
| 91 |
-
# Logging
|
| 92 |
-
# -------------------------------------------------
|
| 93 |
-
|
| 94 |
-
LOG_LEVEL = os.getenv(
|
| 95 |
-
"SKYOS_LOG_LEVEL",
|
| 96 |
-
"INFO"
|
| 97 |
-
)
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
# -------------------------------------------------
|
| 101 |
-
# Debug
|
| 102 |
-
# -------------------------------------------------
|
| 103 |
-
|
| 104 |
-
DEBUG = os.getenv(
|
| 105 |
-
"SKYOS_DEBUG",
|
| 106 |
-
"false"
|
| 107 |
-
).lower() == "true"
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
# -------------------------------------------------
|
| 111 |
-
# Status
|
| 112 |
-
# -------------------------------------------------
|
| 113 |
-
|
| 114 |
-
CONFIG = {
|
| 115 |
-
"project": PROJECT_NAME,
|
| 116 |
-
"timezone": TIMEZONE,
|
| 117 |
-
"redirect_uri": GOOGLE_REDIRECT_URI,
|
| 118 |
-
"credentials": CREDENTIALS_FILE,
|
| 119 |
-
"token": TOKEN_FILE,
|
| 120 |
-
"debug": DEBUG,
|
| 121 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/google/discovery.py
DELETED
|
@@ -1,110 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
discovery.py
|
| 3 |
-
SkyOS Google Discovery
|
| 4 |
-
|
| 5 |
-
Prüft verfügbare Google-Dienste und den
|
| 6 |
-
Authentifizierungsstatus.
|
| 7 |
-
|
| 8 |
-
Autor: Sky Meilin
|
| 9 |
-
Projekt: SkyOS Full Core
|
| 10 |
-
"""
|
| 11 |
-
|
| 12 |
-
from __future__ import annotations
|
| 13 |
-
|
| 14 |
-
import logging
|
| 15 |
-
|
| 16 |
-
from Module.google.auth import (
|
| 17 |
-
drive,
|
| 18 |
-
docs,
|
| 19 |
-
sheets,
|
| 20 |
-
gmail,
|
| 21 |
-
calendar,
|
| 22 |
-
)
|
| 23 |
-
|
| 24 |
-
logger = logging.getLogger("SkyOS.GoogleDiscovery")
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
class GoogleDiscovery:
|
| 28 |
-
|
| 29 |
-
def __init__(self):
|
| 30 |
-
self.services = {
|
| 31 |
-
"drive": drive,
|
| 32 |
-
"docs": docs,
|
| 33 |
-
"sheets": sheets,
|
| 34 |
-
"gmail": gmail,
|
| 35 |
-
"calendar": calendar,
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
# -------------------------------------------------
|
| 39 |
-
# Einzelnen Dienst prüfen
|
| 40 |
-
# -------------------------------------------------
|
| 41 |
-
|
| 42 |
-
def check_service(self, name: str):
|
| 43 |
-
|
| 44 |
-
if name not in self.services:
|
| 45 |
-
return {
|
| 46 |
-
"service": name,
|
| 47 |
-
"available": False,
|
| 48 |
-
"error": "Unknown service",
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
try:
|
| 52 |
-
self.services[name]()
|
| 53 |
-
|
| 54 |
-
return {
|
| 55 |
-
"service": name,
|
| 56 |
-
"available": True,
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
except Exception as e:
|
| 60 |
-
|
| 61 |
-
logger.exception(e)
|
| 62 |
-
|
| 63 |
-
return {
|
| 64 |
-
"service": name,
|
| 65 |
-
"available": False,
|
| 66 |
-
"error": str(e),
|
| 67 |
-
}
|
| 68 |
-
|
| 69 |
-
# -------------------------------------------------
|
| 70 |
-
# Alle Dienste prüfen
|
| 71 |
-
# -------------------------------------------------
|
| 72 |
-
|
| 73 |
-
def discover(self):
|
| 74 |
-
|
| 75 |
-
result = {}
|
| 76 |
-
|
| 77 |
-
for name in self.services:
|
| 78 |
-
|
| 79 |
-
result[name] = self.check_service(name)
|
| 80 |
-
|
| 81 |
-
return result
|
| 82 |
-
|
| 83 |
-
# -------------------------------------------------
|
| 84 |
-
# Statusübersicht
|
| 85 |
-
# -------------------------------------------------
|
| 86 |
-
|
| 87 |
-
def health(self):
|
| 88 |
-
|
| 89 |
-
status = self.discover()
|
| 90 |
-
|
| 91 |
-
ok = all(
|
| 92 |
-
item["available"]
|
| 93 |
-
for item in status.values()
|
| 94 |
-
)
|
| 95 |
-
|
| 96 |
-
return {
|
| 97 |
-
"google_available": ok,
|
| 98 |
-
"services": status,
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
# -------------------------------------------------
|
| 102 |
-
# Unterstützte Dienste
|
| 103 |
-
# -------------------------------------------------
|
| 104 |
-
|
| 105 |
-
def supported_services(self):
|
| 106 |
-
|
| 107 |
-
return list(self.services.keys())
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
google_discovery = GoogleDiscovery()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/google/docs_gateway.py
DELETED
|
@@ -1,154 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
docs_gateway.py
|
| 3 |
-
SkyOS Google Docs Gateway
|
| 4 |
-
|
| 5 |
-
Google Docs Schnittstelle für SkyOS.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS Full Core
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from __future__ import annotations
|
| 12 |
-
|
| 13 |
-
import logging
|
| 14 |
-
|
| 15 |
-
from Module.google.auth import docs
|
| 16 |
-
|
| 17 |
-
logger = logging.getLogger("SkyOS.GoogleDocs")
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
class DocsGateway:
|
| 21 |
-
|
| 22 |
-
def __init__(self):
|
| 23 |
-
self.service = docs()
|
| 24 |
-
|
| 25 |
-
# --------------------------------------------------
|
| 26 |
-
# Dokument erstellen
|
| 27 |
-
# --------------------------------------------------
|
| 28 |
-
|
| 29 |
-
def create_document(self, title: str):
|
| 30 |
-
"""Erstellt ein neues Google-Dokument."""
|
| 31 |
-
|
| 32 |
-
document = (
|
| 33 |
-
self.service.documents()
|
| 34 |
-
.create(
|
| 35 |
-
body={
|
| 36 |
-
"title": title
|
| 37 |
-
}
|
| 38 |
-
)
|
| 39 |
-
.execute()
|
| 40 |
-
)
|
| 41 |
-
|
| 42 |
-
logger.info("Dokument erstellt: %s", title)
|
| 43 |
-
|
| 44 |
-
return document
|
| 45 |
-
|
| 46 |
-
# --------------------------------------------------
|
| 47 |
-
# Dokument abrufen
|
| 48 |
-
# --------------------------------------------------
|
| 49 |
-
|
| 50 |
-
def get_document(self, document_id: str):
|
| 51 |
-
"""Lädt ein Google-Dokument."""
|
| 52 |
-
|
| 53 |
-
return (
|
| 54 |
-
self.service.documents()
|
| 55 |
-
.get(
|
| 56 |
-
documentId=document_id
|
| 57 |
-
)
|
| 58 |
-
.execute()
|
| 59 |
-
)
|
| 60 |
-
|
| 61 |
-
# --------------------------------------------------
|
| 62 |
-
# Text anhängen
|
| 63 |
-
# --------------------------------------------------
|
| 64 |
-
|
| 65 |
-
def append_text(
|
| 66 |
-
self,
|
| 67 |
-
document_id: str,
|
| 68 |
-
text: str,
|
| 69 |
-
index: int = 1,
|
| 70 |
-
):
|
| 71 |
-
"""
|
| 72 |
-
Fügt Text in das Dokument ein.
|
| 73 |
-
Standardmäßig am Dokumentanfang.
|
| 74 |
-
"""
|
| 75 |
-
|
| 76 |
-
requests = [
|
| 77 |
-
{
|
| 78 |
-
"insertText": {
|
| 79 |
-
"location": {
|
| 80 |
-
"index": index
|
| 81 |
-
},
|
| 82 |
-
"text": text,
|
| 83 |
-
}
|
| 84 |
-
}
|
| 85 |
-
]
|
| 86 |
-
|
| 87 |
-
return (
|
| 88 |
-
self.service.documents()
|
| 89 |
-
.batchUpdate(
|
| 90 |
-
documentId=document_id,
|
| 91 |
-
body={
|
| 92 |
-
"requests": requests
|
| 93 |
-
},
|
| 94 |
-
)
|
| 95 |
-
.execute()
|
| 96 |
-
)
|
| 97 |
-
|
| 98 |
-
# --------------------------------------------------
|
| 99 |
-
# Dokument ersetzen
|
| 100 |
-
# --------------------------------------------------
|
| 101 |
-
|
| 102 |
-
def replace_text(
|
| 103 |
-
self,
|
| 104 |
-
document_id: str,
|
| 105 |
-
search_text: str,
|
| 106 |
-
replace_text: str,
|
| 107 |
-
):
|
| 108 |
-
"""Ersetzt Text im gesamten Dokument."""
|
| 109 |
-
|
| 110 |
-
requests = [
|
| 111 |
-
{
|
| 112 |
-
"replaceAllText": {
|
| 113 |
-
"containsText": {
|
| 114 |
-
"text": search_text,
|
| 115 |
-
"matchCase": True,
|
| 116 |
-
},
|
| 117 |
-
"replaceText": replace_text,
|
| 118 |
-
}
|
| 119 |
-
}
|
| 120 |
-
]
|
| 121 |
-
|
| 122 |
-
return (
|
| 123 |
-
self.service.documents()
|
| 124 |
-
.batchUpdate(
|
| 125 |
-
documentId=document_id,
|
| 126 |
-
body={
|
| 127 |
-
"requests": requests
|
| 128 |
-
},
|
| 129 |
-
)
|
| 130 |
-
.execute()
|
| 131 |
-
)
|
| 132 |
-
|
| 133 |
-
# --------------------------------------------------
|
| 134 |
-
# Titel ändern
|
| 135 |
-
# --------------------------------------------------
|
| 136 |
-
|
| 137 |
-
def rename_document(
|
| 138 |
-
self,
|
| 139 |
-
document_id: str,
|
| 140 |
-
new_title: str,
|
| 141 |
-
):
|
| 142 |
-
"""
|
| 143 |
-
Titel ändern.
|
| 144 |
-
|
| 145 |
-
Benötigt zusätzlich drive_gateway.py,
|
| 146 |
-
da der Dateiname über Google Drive verwaltet wird.
|
| 147 |
-
"""
|
| 148 |
-
|
| 149 |
-
raise NotImplementedError(
|
| 150 |
-
"rename_document erfolgt über drive_gateway.py"
|
| 151 |
-
)
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
docs_gateway = DocsGateway()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/google/drive_gateway.py
DELETED
|
@@ -1,57 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
gateway.py
|
| 3 |
-
SkyOS Google Gateway
|
| 4 |
-
|
| 5 |
-
Zentraler Zugriff auf alle Google-Services.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS Full Core
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from Module.google.auth import (
|
| 12 |
-
drive,
|
| 13 |
-
docs,
|
| 14 |
-
sheets,
|
| 15 |
-
gmail,
|
| 16 |
-
calendar,
|
| 17 |
-
)
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
class GoogleGateway:
|
| 21 |
-
"""Zentrale Schnittstelle für alle Google APIs."""
|
| 22 |
-
|
| 23 |
-
@staticmethod
|
| 24 |
-
def drive():
|
| 25 |
-
return drive()
|
| 26 |
-
|
| 27 |
-
@staticmethod
|
| 28 |
-
def docs():
|
| 29 |
-
return docs()
|
| 30 |
-
|
| 31 |
-
@staticmethod
|
| 32 |
-
def sheets():
|
| 33 |
-
return sheets()
|
| 34 |
-
|
| 35 |
-
@staticmethod
|
| 36 |
-
def gmail():
|
| 37 |
-
return gmail()
|
| 38 |
-
|
| 39 |
-
@staticmethod
|
| 40 |
-
def calendar():
|
| 41 |
-
return calendar()
|
| 42 |
-
|
| 43 |
-
@staticmethod
|
| 44 |
-
def services():
|
| 45 |
-
"""Liefert alle initialisierten Services."""
|
| 46 |
-
|
| 47 |
-
return {
|
| 48 |
-
"drive": drive(),
|
| 49 |
-
"docs": docs(),
|
| 50 |
-
"sheets": sheets(),
|
| 51 |
-
"gmail": gmail(),
|
| 52 |
-
"calendar": calendar(),
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
# Singleton
|
| 57 |
-
google = GoogleGateway()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/google/gmail_gateway.py
DELETED
|
@@ -1,162 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
gmail_gateway.py
|
| 3 |
-
SkyOS Google Gmail Gateway
|
| 4 |
-
|
| 5 |
-
Gmail-Schnittstelle für private Google-Konten.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS Full Core
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from __future__ import annotations
|
| 12 |
-
|
| 13 |
-
import base64
|
| 14 |
-
import logging
|
| 15 |
-
from email.mime.text import MIMEText
|
| 16 |
-
|
| 17 |
-
from Module.google.auth import gmail
|
| 18 |
-
|
| 19 |
-
logger = logging.getLogger("SkyOS.Gmail")
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
class GmailGateway:
|
| 23 |
-
|
| 24 |
-
def __init__(self):
|
| 25 |
-
self.service = gmail()
|
| 26 |
-
|
| 27 |
-
# --------------------------------------------------
|
| 28 |
-
# Nachrichten
|
| 29 |
-
# --------------------------------------------------
|
| 30 |
-
|
| 31 |
-
def list_messages(
|
| 32 |
-
self,
|
| 33 |
-
query: str = "",
|
| 34 |
-
max_results: int = 10,
|
| 35 |
-
):
|
| 36 |
-
"""Listet Nachrichten auf."""
|
| 37 |
-
|
| 38 |
-
response = (
|
| 39 |
-
self.service.users()
|
| 40 |
-
.messages()
|
| 41 |
-
.list(
|
| 42 |
-
userId="me",
|
| 43 |
-
q=query,
|
| 44 |
-
maxResults=max_results,
|
| 45 |
-
)
|
| 46 |
-
.execute()
|
| 47 |
-
)
|
| 48 |
-
|
| 49 |
-
return response.get("messages", [])
|
| 50 |
-
|
| 51 |
-
def get_message(self, message_id: str):
|
| 52 |
-
"""Liest eine Nachricht."""
|
| 53 |
-
|
| 54 |
-
return (
|
| 55 |
-
self.service.users()
|
| 56 |
-
.messages()
|
| 57 |
-
.get(
|
| 58 |
-
userId="me",
|
| 59 |
-
id=message_id,
|
| 60 |
-
format="full",
|
| 61 |
-
)
|
| 62 |
-
.execute()
|
| 63 |
-
)
|
| 64 |
-
|
| 65 |
-
# --------------------------------------------------
|
| 66 |
-
# Senden
|
| 67 |
-
# --------------------------------------------------
|
| 68 |
-
|
| 69 |
-
def send_email(
|
| 70 |
-
self,
|
| 71 |
-
to: str,
|
| 72 |
-
subject: str,
|
| 73 |
-
body: str,
|
| 74 |
-
):
|
| 75 |
-
"""Sendet eine E-Mail."""
|
| 76 |
-
|
| 77 |
-
message = MIMEText(body)
|
| 78 |
-
|
| 79 |
-
message["to"] = to
|
| 80 |
-
message["subject"] = subject
|
| 81 |
-
|
| 82 |
-
raw = base64.urlsafe_b64encode(
|
| 83 |
-
message.as_bytes()
|
| 84 |
-
).decode()
|
| 85 |
-
|
| 86 |
-
return (
|
| 87 |
-
self.service.users()
|
| 88 |
-
.messages()
|
| 89 |
-
.send(
|
| 90 |
-
userId="me",
|
| 91 |
-
body={
|
| 92 |
-
"raw": raw
|
| 93 |
-
},
|
| 94 |
-
)
|
| 95 |
-
.execute()
|
| 96 |
-
)
|
| 97 |
-
|
| 98 |
-
# --------------------------------------------------
|
| 99 |
-
# Labels
|
| 100 |
-
# --------------------------------------------------
|
| 101 |
-
|
| 102 |
-
def list_labels(self):
|
| 103 |
-
"""Listet alle Labels."""
|
| 104 |
-
|
| 105 |
-
response = (
|
| 106 |
-
self.service.users()
|
| 107 |
-
.labels()
|
| 108 |
-
.list(
|
| 109 |
-
userId="me"
|
| 110 |
-
)
|
| 111 |
-
.execute()
|
| 112 |
-
)
|
| 113 |
-
|
| 114 |
-
return response.get("labels", [])
|
| 115 |
-
|
| 116 |
-
# --------------------------------------------------
|
| 117 |
-
# Löschen
|
| 118 |
-
# --------------------------------------------------
|
| 119 |
-
|
| 120 |
-
def delete_message(
|
| 121 |
-
self,
|
| 122 |
-
message_id: str,
|
| 123 |
-
):
|
| 124 |
-
"""Löscht eine Nachricht."""
|
| 125 |
-
|
| 126 |
-
return (
|
| 127 |
-
self.service.users()
|
| 128 |
-
.messages()
|
| 129 |
-
.delete(
|
| 130 |
-
userId="me",
|
| 131 |
-
id=message_id,
|
| 132 |
-
)
|
| 133 |
-
.execute()
|
| 134 |
-
)
|
| 135 |
-
|
| 136 |
-
# --------------------------------------------------
|
| 137 |
-
# Archivieren
|
| 138 |
-
# --------------------------------------------------
|
| 139 |
-
|
| 140 |
-
def archive_message(
|
| 141 |
-
self,
|
| 142 |
-
message_id: str,
|
| 143 |
-
):
|
| 144 |
-
"""Archiviert eine Nachricht."""
|
| 145 |
-
|
| 146 |
-
return (
|
| 147 |
-
self.service.users()
|
| 148 |
-
.messages()
|
| 149 |
-
.modify(
|
| 150 |
-
userId="me",
|
| 151 |
-
id=message_id,
|
| 152 |
-
body={
|
| 153 |
-
"removeLabelIds": [
|
| 154 |
-
"INBOX"
|
| 155 |
-
]
|
| 156 |
-
},
|
| 157 |
-
)
|
| 158 |
-
.execute()
|
| 159 |
-
)
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
gmail_gateway = GmailGateway()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/google/oauth.py
DELETED
|
@@ -1,117 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
oauth.py
|
| 3 |
-
SkyOS Google OAuth
|
| 4 |
-
|
| 5 |
-
OAuth-Authentifizierung für private Google-Konten.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS Full Core
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from __future__ import annotations
|
| 12 |
-
|
| 13 |
-
import logging
|
| 14 |
-
from pathlib import Path
|
| 15 |
-
|
| 16 |
-
from google.auth.transport.requests import Request
|
| 17 |
-
from google.oauth2.credentials import Credentials
|
| 18 |
-
from google_auth_oauthlib.flow import InstalledAppFlow
|
| 19 |
-
|
| 20 |
-
from Module.google.config import (
|
| 21 |
-
GOOGLE_SCOPES,
|
| 22 |
-
CREDENTIALS_FILE,
|
| 23 |
-
TOKEN_FILE,
|
| 24 |
-
)
|
| 25 |
-
|
| 26 |
-
logger = logging.getLogger("SkyOS.GoogleOAuth")
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
class GoogleOAuth:
|
| 30 |
-
|
| 31 |
-
def __init__(self):
|
| 32 |
-
self.credentials = None
|
| 33 |
-
|
| 34 |
-
def load_credentials(self) -> Credentials | None:
|
| 35 |
-
"""Lädt vorhandene Zugangsdaten."""
|
| 36 |
-
|
| 37 |
-
token_path = Path(TOKEN_FILE)
|
| 38 |
-
|
| 39 |
-
if token_path.exists():
|
| 40 |
-
self.credentials = Credentials.from_authorized_user_file(
|
| 41 |
-
TOKEN_FILE,
|
| 42 |
-
GOOGLE_SCOPES,
|
| 43 |
-
)
|
| 44 |
-
|
| 45 |
-
return self.credentials
|
| 46 |
-
|
| 47 |
-
def refresh_credentials(self) -> Credentials | None:
|
| 48 |
-
"""Erneuert ein abgelaufenes Token."""
|
| 49 |
-
|
| 50 |
-
if (
|
| 51 |
-
self.credentials
|
| 52 |
-
and self.credentials.expired
|
| 53 |
-
and self.credentials.refresh_token
|
| 54 |
-
):
|
| 55 |
-
logger.info("Google Token wird aktualisiert...")
|
| 56 |
-
self.credentials.refresh(Request())
|
| 57 |
-
self.save_credentials()
|
| 58 |
-
|
| 59 |
-
return self.credentials
|
| 60 |
-
|
| 61 |
-
def authenticate(self) -> Credentials:
|
| 62 |
-
"""Startet den OAuth-Login, falls erforderlich."""
|
| 63 |
-
|
| 64 |
-
self.load_credentials()
|
| 65 |
-
|
| 66 |
-
if (
|
| 67 |
-
self.credentials
|
| 68 |
-
and self.credentials.valid
|
| 69 |
-
):
|
| 70 |
-
return self.credentials
|
| 71 |
-
|
| 72 |
-
if (
|
| 73 |
-
self.credentials
|
| 74 |
-
and self.credentials.expired
|
| 75 |
-
and self.credentials.refresh_token
|
| 76 |
-
):
|
| 77 |
-
self.refresh_credentials()
|
| 78 |
-
return self.credentials
|
| 79 |
-
|
| 80 |
-
logger.info("Starte Google OAuth Login...")
|
| 81 |
-
|
| 82 |
-
flow = InstalledAppFlow.from_client_secrets_file(
|
| 83 |
-
CREDENTIALS_FILE,
|
| 84 |
-
GOOGLE_SCOPES,
|
| 85 |
-
)
|
| 86 |
-
|
| 87 |
-
self.credentials = flow.run_local_server(
|
| 88 |
-
port=0,
|
| 89 |
-
prompt="consent",
|
| 90 |
-
)
|
| 91 |
-
|
| 92 |
-
self.save_credentials()
|
| 93 |
-
|
| 94 |
-
return self.credentials
|
| 95 |
-
|
| 96 |
-
def save_credentials(self):
|
| 97 |
-
"""Speichert das OAuth-Token."""
|
| 98 |
-
|
| 99 |
-
if self.credentials is None:
|
| 100 |
-
return
|
| 101 |
-
|
| 102 |
-
with open(TOKEN_FILE, "w", encoding="utf-8") as token:
|
| 103 |
-
token.write(self.credentials.to_json())
|
| 104 |
-
|
| 105 |
-
logger.info("Google Token gespeichert.")
|
| 106 |
-
|
| 107 |
-
def revoke(self):
|
| 108 |
-
"""Entfernt das lokale Token."""
|
| 109 |
-
|
| 110 |
-
token_path = Path(TOKEN_FILE)
|
| 111 |
-
|
| 112 |
-
if token_path.exists():
|
| 113 |
-
token_path.unlink()
|
| 114 |
-
logger.info("Google Token gelöscht.")
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
oauth = GoogleOAuth()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/google/utils.py
DELETED
|
@@ -1,130 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
utils.py
|
| 3 |
-
SkyOS Google Utilities
|
| 4 |
-
|
| 5 |
-
Gemeinsame Hilfsfunktionen für alle Google-Module.
|
| 6 |
-
|
| 7 |
-
Autor: Sky Meilin
|
| 8 |
-
Projekt: SkyOS Full Core
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
from __future__ import annotations
|
| 12 |
-
|
| 13 |
-
import logging
|
| 14 |
-
import re
|
| 15 |
-
from datetime import datetime
|
| 16 |
-
from pathlib import Path
|
| 17 |
-
from typing import Any
|
| 18 |
-
|
| 19 |
-
logger = logging.getLogger("SkyOS.GoogleUtils")
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
# -------------------------------------------------
|
| 23 |
-
# Datum & Zeit
|
| 24 |
-
# -------------------------------------------------
|
| 25 |
-
|
| 26 |
-
def iso_now() -> str:
|
| 27 |
-
"""Aktuelle UTC-Zeit im ISO-8601-Format."""
|
| 28 |
-
return datetime.utcnow().replace(microsecond=0).isoformat() + "Z"
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
def format_datetime(dt: datetime) -> str:
|
| 32 |
-
"""Konvertiert datetime in ISO-8601."""
|
| 33 |
-
return dt.isoformat()
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
# -------------------------------------------------
|
| 37 |
-
# Dateinamen
|
| 38 |
-
# -------------------------------------------------
|
| 39 |
-
|
| 40 |
-
def sanitize_filename(filename: str) -> str:
|
| 41 |
-
"""Entfernt ungültige Zeichen aus Dateinamen."""
|
| 42 |
-
|
| 43 |
-
filename = re.sub(r'[<>:"/\\|?*]', "_", filename)
|
| 44 |
-
filename = filename.strip()
|
| 45 |
-
|
| 46 |
-
return filename
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
def file_extension(path: str) -> str:
|
| 50 |
-
"""Gibt die Dateiendung zurück."""
|
| 51 |
-
|
| 52 |
-
return Path(path).suffix.lower()
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
# -------------------------------------------------
|
| 56 |
-
# Google IDs
|
| 57 |
-
# -------------------------------------------------
|
| 58 |
-
|
| 59 |
-
def is_google_id(value: str) -> bool:
|
| 60 |
-
"""
|
| 61 |
-
Einfache Prüfung, ob ein String
|
| 62 |
-
wie eine Google-ID aussieht.
|
| 63 |
-
"""
|
| 64 |
-
|
| 65 |
-
return (
|
| 66 |
-
isinstance(value, str)
|
| 67 |
-
and len(value) > 20
|
| 68 |
-
)
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
# -------------------------------------------------
|
| 72 |
-
# API Antworten
|
| 73 |
-
# -------------------------------------------------
|
| 74 |
-
|
| 75 |
-
def success(data: Any = None, message: str = "OK") -> dict:
|
| 76 |
-
"""Standardisierte Erfolgsausgabe."""
|
| 77 |
-
|
| 78 |
-
return {
|
| 79 |
-
"success": True,
|
| 80 |
-
"message": message,
|
| 81 |
-
"data": data,
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
def error(message: str, exception: Exception | None = None) -> dict:
|
| 86 |
-
"""Standardisierte Fehlerausgabe."""
|
| 87 |
-
|
| 88 |
-
logger.error(message)
|
| 89 |
-
|
| 90 |
-
return {
|
| 91 |
-
"success": False,
|
| 92 |
-
"message": message,
|
| 93 |
-
"error": str(exception) if exception else None,
|
| 94 |
-
}
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
# -------------------------------------------------
|
| 98 |
-
# Logging
|
| 99 |
-
# -------------------------------------------------
|
| 100 |
-
|
| 101 |
-
def log_api_call(service: str, action: str):
|
| 102 |
-
|
| 103 |
-
logger.info(
|
| 104 |
-
"[%s] %s",
|
| 105 |
-
service.upper(),
|
| 106 |
-
action,
|
| 107 |
-
)
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
# -------------------------------------------------
|
| 111 |
-
# MIME Types
|
| 112 |
-
# -------------------------------------------------
|
| 113 |
-
|
| 114 |
-
GOOGLE_MIME_TYPES = {
|
| 115 |
-
|
| 116 |
-
"document":
|
| 117 |
-
"application/vnd.google-apps.document",
|
| 118 |
-
|
| 119 |
-
"spreadsheet":
|
| 120 |
-
"application/vnd.google-apps.spreadsheet",
|
| 121 |
-
|
| 122 |
-
"folder":
|
| 123 |
-
"application/vnd.google-apps.folder",
|
| 124 |
-
|
| 125 |
-
"presentation":
|
| 126 |
-
"application/vnd.google-apps.presentation",
|
| 127 |
-
|
| 128 |
-
"drawing":
|
| 129 |
-
"application/vnd.google-apps.drawing",
|
| 130 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/remote_control.py
CHANGED
|
@@ -4,47 +4,59 @@ from typing import Dict, Any
|
|
| 4 |
|
| 5 |
|
| 6 |
class RemoteControl:
|
| 7 |
-
# Klassen-Attribut verhindert den Verlust der Geräteliste bei Neu-Instanziierung
|
| 8 |
-
_connected_devices: Dict[str, Dict[str, str]] = {}
|
| 9 |
|
| 10 |
def __init__(self):
|
|
|
|
| 11 |
print("RemoteControl geladen")
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
-
def connect_device(self, device_id: str, device_type: str) -> Dict[str, str]:
|
| 18 |
self.connected_devices[device_id] = {
|
| 19 |
"type": device_type,
|
| 20 |
"status": "connected"
|
| 21 |
}
|
|
|
|
| 22 |
return {
|
| 23 |
"device_id": device_id,
|
| 24 |
"status": "connected"
|
| 25 |
}
|
| 26 |
|
| 27 |
-
def disconnect_device(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
if device_id in self.connected_devices:
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
return {
|
| 31 |
"device_id": device_id,
|
| 32 |
"status": "disconnected"
|
| 33 |
}
|
|
|
|
| 34 |
return {
|
| 35 |
"error": "Gerät nicht gefunden"
|
| 36 |
}
|
| 37 |
|
| 38 |
-
def send_command(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
if device_id not in self.connected_devices:
|
| 40 |
-
return {
|
| 41 |
-
"error": "Gerät nicht verbunden"
|
| 42 |
-
}
|
| 43 |
|
| 44 |
-
# Falls ein Gerät den Status 'disconnected' hat, wird das Senden blockiert
|
| 45 |
-
if self.connected_devices[device_id].get("status") != "connected":
|
| 46 |
return {
|
| 47 |
-
"error": "Gerät
|
| 48 |
}
|
| 49 |
|
| 50 |
return {
|
|
@@ -53,5 +65,6 @@ class RemoteControl:
|
|
| 53 |
"status": "sent"
|
| 54 |
}
|
| 55 |
|
| 56 |
-
def list_devices(self)
|
| 57 |
-
|
|
|
|
|
|
| 4 |
|
| 5 |
|
| 6 |
class RemoteControl:
|
|
|
|
|
|
|
| 7 |
|
| 8 |
def __init__(self):
|
| 9 |
+
|
| 10 |
print("RemoteControl geladen")
|
| 11 |
|
| 12 |
+
self.connected_devices = {}
|
| 13 |
+
|
| 14 |
+
def connect_device(
|
| 15 |
+
self,
|
| 16 |
+
device_id: str,
|
| 17 |
+
device_type: str
|
| 18 |
+
):
|
| 19 |
|
|
|
|
| 20 |
self.connected_devices[device_id] = {
|
| 21 |
"type": device_type,
|
| 22 |
"status": "connected"
|
| 23 |
}
|
| 24 |
+
|
| 25 |
return {
|
| 26 |
"device_id": device_id,
|
| 27 |
"status": "connected"
|
| 28 |
}
|
| 29 |
|
| 30 |
+
def disconnect_device(
|
| 31 |
+
self,
|
| 32 |
+
device_id: str
|
| 33 |
+
):
|
| 34 |
+
|
| 35 |
if device_id in self.connected_devices:
|
| 36 |
+
|
| 37 |
+
self.connected_devices[device_id][
|
| 38 |
+
"status"
|
| 39 |
+
] = "disconnected"
|
| 40 |
+
|
| 41 |
return {
|
| 42 |
"device_id": device_id,
|
| 43 |
"status": "disconnected"
|
| 44 |
}
|
| 45 |
+
|
| 46 |
return {
|
| 47 |
"error": "Gerät nicht gefunden"
|
| 48 |
}
|
| 49 |
|
| 50 |
+
def send_command(
|
| 51 |
+
self,
|
| 52 |
+
device_id: str,
|
| 53 |
+
command: str
|
| 54 |
+
) -> Dict[str, Any]:
|
| 55 |
+
|
| 56 |
if device_id not in self.connected_devices:
|
|
|
|
|
|
|
|
|
|
| 57 |
|
|
|
|
|
|
|
| 58 |
return {
|
| 59 |
+
"error": "Gerät nicht verbunden"
|
| 60 |
}
|
| 61 |
|
| 62 |
return {
|
|
|
|
| 65 |
"status": "sent"
|
| 66 |
}
|
| 67 |
|
| 68 |
+
def list_devices(self):
|
| 69 |
+
|
| 70 |
+
return self.connected_devices
|