refactor: "Gamebox" ueberall durch "AI-Box(en)" ersetzen

Rein terminologisch: die feste "Gamebox" ist konzeptuell zu beliebig vielen
AI-Boxen geworden (Multi-Node-Compute-Fleet). Wort-Sweep ueber Code-Kommentare,
Strings, README, docs, .env.example, CHANGELOG — keine Identifier/Keys betroffen
(Gamebox kam nirgends in Variablennamen vor). Casing erhalten: Gamebox→AI-Box,
gamebox→ai-box. NODE_NAME-Default gamebox→ai-box, Instanz-IDs f5tts@ai-box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 00:34:21 +02:00
co-authored by Claude Opus 4.8
parent 17a60e5d3e
commit a773832bca
19 changed files with 76 additions and 76 deletions
+1 -1
View File
@@ -235,7 +235,7 @@ Nur **App neu bauen** (kein Backend). APK 0.2.2.0.
## [0.2.0.4 – 0.2.0.5] — 2026-07-11 — Plan B: Lokales LLM („Gemini-Feeling") ## [0.2.0.4 – 0.2.0.5] — 2026-07-11 — Plan B: Lokales LLM („Gemini-Feeling")
Ein kleines, schnelles Modell (**Qwen3 8B** via llama.cpp/llama-swap auf der Gamebox-GPU) übernimmt einfache Turns in **<1 s**; alles Schwere/Technische/Werkzeug-artige reicht ein Router automatisch an **Claude** weiter. Ziel: schnelle Antworten ohne die Claude-Max-Subscription aufzugeben. Ein kleines, schnelles Modell (**Qwen3 8B** via llama.cpp/llama-swap auf der AI-Box-GPU) übernimmt einfache Turns in **<1 s**; alles Schwere/Technische/Werkzeug-artige reicht ein Router automatisch an **Claude** weiter. Ziel: schnelle Antworten ohne die Claude-Max-Subscription aufzugeben.
### Hinzugefügt ### Hinzugefügt
+4 -4
View File
@@ -805,7 +805,7 @@ cp ARIA-v0.0.3.0.apk ~/ARIA-AGENT/rvs/updates/
Die GPU-Dienste laufen auf einem oder mehreren separaten Rechnern mit NVIDIA GPU. Die GPU-Dienste laufen auf einem oder mehreren separaten Rechnern mit NVIDIA GPU.
Jeder **Compute-Node** verbindet sich ueber RVS mit der ARIA-Infrastruktur — kein Jeder **Compute-Node** verbindet sich ueber RVS mit der ARIA-Infrastruktur — kein
VPN noetig, funktioniert ueber verschiedene Netze hinweg. Frueher war das *eine* VPN noetig, funktioniert ueber verschiedene Netze hinweg. Frueher war das *eine*
feste „Gamebox"; jetzt sind es beliebig viele Nodes, jeder per `.env` konfiguriert. feste „AI-Box"; jetzt sind es beliebig viele Nodes, jeder per `.env` konfiguriert.
### Dienste & Profile ### Dienste & Profile
@@ -828,7 +828,7 @@ LLM-Box COMPOSE_PROFILES=llm LLM_GPU=0
-- kleine Karte: Whisper (klein) statt Voxtral, neben F5-TTS -- -- kleine Karte: Whisper (klein) statt Voxtral, neben F5-TTS --
Klein-Box COMPOSE_PROFILES=whisper,f5tts WHISPER_GPU=0 F5TTS_GPU=0 Klein-Box COMPOSE_PROFILES=whisper,f5tts WHISPER_GPU=0 F5TTS_GPU=0
── oder All-in-One ── ── oder All-in-One ──
Gamebox COMPOSE_PROFILES=voxtral,f5tts,llm VOXTRAL_GPU=1 F5TTS_GPU=0 LLM_GPU=0 AI-Box COMPOSE_PROFILES=voxtral,f5tts,llm VOXTRAL_GPU=1 F5TTS_GPU=0 LLM_GPU=0
↕ RVS (Rechenzentrum, WebSocket Relay) ↕ RVS (Rechenzentrum, WebSocket Relay)
@@ -1052,7 +1052,7 @@ docker exec aria-brain curl localhost:8080/memory/stats
- [x] Pre-Roll-Buffer einstellbar in App-Settings - [x] Pre-Roll-Buffer einstellbar in App-Settings
- [x] Decimal-zu-Worte fuer TTS + generisches Acronym-Buchstabieren - [x] Decimal-zu-Worte fuer TTS + generisches Acronym-Buchstabieren
- [x] voice_preload/voice_ready: visueller Status-Indikator beim Stimmen-Wechsel - [x] voice_preload/voice_ready: visueller Status-Indikator beim Stimmen-Wechsel
- [x] Whisper STT auf die Gamebox ausgelagert (CUDA float16, fast Echtzeit) - [x] Whisper STT auf die AI-Box ausgelagert (CUDA float16, fast Echtzeit)
- [x] **F5-TTS ersetzt XTTS** — bessere Voice-Cloning-Qualitaet, Whisper-auto-transkribierter Referenz-Text - [x] **F5-TTS ersetzt XTTS** — bessere Voice-Cloning-Qualitaet, Whisper-auto-transkribierter Referenz-Text
- [x] Audio-Pause statt Ducking (TRANSIENT statt MAY_DUCK) + release-Timing fix - [x] Audio-Pause statt Ducking (TRANSIENT statt MAY_DUCK) + release-Timing fix
- [x] VAD-Stille-Toleranz einstellbar (1-8s) + adaptive Mikro-Baseline + Max-Aufnahme einstellbar (1-30 min) - [x] VAD-Stille-Toleranz einstellbar (1-8s) + adaptive Mikro-Baseline + Max-Aufnahme einstellbar (1-30 min)
@@ -1107,7 +1107,7 @@ docker exec aria-brain curl localhost:8080/memory/stats
- [x] App: Chat-Suche mit Next/Prev Navigation statt Filter - [x] App: Chat-Suche mit Next/Prev Navigation statt Filter
- [x] Token/Call-Metrics + Subscription-Quota-Tracking (Pro / Max 5x / Max 20x / Custom) - [x] Token/Call-Metrics + Subscription-Quota-Tracking (Pro / Max 5x / Max 20x / Custom)
- [x] Datei-Manager Multi-Select: Bulk-Download als ZIP + Bulk-Delete (Diagnostic + App) - [x] Datei-Manager Multi-Select: Bulk-Download als ZIP + Bulk-Delete (Diagnostic + App)
- [x] **FLUX.1 Bildgenerierung**: eigener `flux-bridge`-Container auf der Gamebox (analog xtts/whisper) mit Hot-Swap zwischen FLUX.1-dev (Quali) und FLUX.1-schnell (Tempo). Default-Modell + Raw-/Switch-Keywords + HuggingFace-Token in Diagnostic-UI verwaltet, automatischer Pipeline-Reload bei Modell-Wechsel. ARIA bekommt `flux_generate`-Tool, Output landet als `/shared/uploads/aria_generated_<ts>.png` und wird via `[FILE: ...]`-Marker als Anhang-Bubble in App + Diagnostic gerendert. Download-Status (mehrere GB) sichtbar als 🎉-Toast wenn fertig - [x] **FLUX.1 Bildgenerierung**: eigener `flux-bridge`-Container auf der AI-Box (analog xtts/whisper) mit Hot-Swap zwischen FLUX.1-dev (Quali) und FLUX.1-schnell (Tempo). Default-Modell + Raw-/Switch-Keywords + HuggingFace-Token in Diagnostic-UI verwaltet, automatischer Pipeline-Reload bei Modell-Wechsel. ARIA bekommt `flux_generate`-Tool, Output landet als `/shared/uploads/aria_generated_<ts>.png` und wird via `[FILE: ...]`-Marker als Anhang-Bubble in App + Diagnostic gerendert. Download-Status (mehrere GB) sichtbar als 🎉-Toast wenn fertig
- [x] **ARIA Live (Diagnostic) + Not-Aus**: read-only Mirror der Claude-Code-Session ersetzt den SSH-Tab. Tool-Calls + Inputs + Outputs (truncated 4 KB) live, farbcodiert. Roter ⛔ Not-Aus-Button schickt `cancel_request` mit `hard:true` → Bridge ruft den proxy-internen `/cancel-all` Side-Channel (Port 3457) → alle Claude-Subprocesses sofort tot. Plus: Idle-Watchdog im Proxy (20 min Inaktivitaet → Subprocess-Kill) + httpx-Timeout-Split im Brain (connect 10s / read 24h) damit lange Pentests durchlaufen - [x] **ARIA Live (Diagnostic) + Not-Aus**: read-only Mirror der Claude-Code-Session ersetzt den SSH-Tab. Tool-Calls + Inputs + Outputs (truncated 4 KB) live, farbcodiert. Roter ⛔ Not-Aus-Button schickt `cancel_request` mit `hard:true` → Bridge ruft den proxy-internen `/cancel-all` Side-Channel (Port 3457) → alle Claude-Subprocesses sofort tot. Plus: Idle-Watchdog im Proxy (20 min Inaktivitaet → Subprocess-Kill) + httpx-Timeout-Split im Brain (connect 10s / read 24h) damit lange Pentests durchlaufen
- [x] **OAuth2-Pipeline ueber RVS-Callback**: Caddy mit Let's Encrypt vor dem RVS, HTTP-Route `/oauth/callback/{service}` broadcastet als `oauth_callback`-WS-Message, aria-bridge forwarded an Brain, Token landet in `/shared/config/oauth_tokens.json` (mode 0600). ARIAs `oauth_register_provider`-Tool legt neue Provider on-demand an (URLs/scopes, nicht Credentials). Diagnostic + App haben beide Provider-Verwaltung inklusive Custom-Provider-Anlage - [x] **OAuth2-Pipeline ueber RVS-Callback**: Caddy mit Let's Encrypt vor dem RVS, HTTP-Route `/oauth/callback/{service}` broadcastet als `oauth_callback`-WS-Message, aria-bridge forwarded an Brain, Token landet in `/shared/config/oauth_tokens.json` (mode 0600). ARIAs `oauth_register_provider`-Tool legt neue Provider on-demand an (URLs/scopes, nicht Credentials). Diagnostic + App haben beide Provider-Verwaltung inklusive Custom-Provider-Anlage
- [x] **Skill-Mgmt-Tools fuer ARIA**: `skill_update` (Code/README/pip_packages mit venv-Rebuild) + `skill_delete` — verhindert Skill-Friedhof mit `-v2`/`-fixed`-Suffixen. Plus App-seitiger SkillBrowser (Run + Live-Output + Logs der letzten 20 Runs) in Settings → 🛠️ Skills - [x] **Skill-Mgmt-Tools fuer ARIA**: `skill_update` (Code/README/pip_packages mit venv-Rebuild) + `skill_delete` — verhindert Skill-Friedhof mit `-v2`/`-fixed`-Suffixen. Plus App-seitiger SkillBrowser (Run + Live-Output + Logs der letzten 20 Runs) in Settings → 🛠️ Skills
+4 -4
View File
@@ -357,7 +357,7 @@ const ChatScreen: React.FC = () => {
// folgende identische Events (z.B. zwei 'thinking' hintereinander) den // folgende identische Events (z.B. zwei 'thinking' hintereinander) den
// Stream zumuellen. Eigentlich seltener Fall, aber billig zu pruefen. // Stream zumuellen. Eigentlich seltener Fall, aber billig zu pruefen.
const lastThoughtKeyRef = useRef<string>(''); const lastThoughtKeyRef = useRef<string>('');
// Service-Status (Gamebox: F5-TTS / Whisper Lade-Status) + Banner-Sichtbarkeit // Service-Status (AI-Box: F5-TTS / Whisper Lade-Status) + Banner-Sichtbarkeit
const [serviceStatus, setServiceStatus] = useState<Record<string, {state: string, model?: string, loadSeconds?: number, error?: string, downloading?: boolean, freshlyDownloaded?: boolean}>>({}); const [serviceStatus, setServiceStatus] = useState<Record<string, {state: string, model?: string, loadSeconds?: number, error?: string, downloading?: boolean, freshlyDownloaded?: boolean}>>({});
const [serviceBannerDismissed, setServiceBannerDismissed] = useState(false); const [serviceBannerDismissed, setServiceBannerDismissed] = useState(false);
// Gerätelokale TTS-Config: globaler Toggle (aus Settings) + temporäres Muten (Mund-Button) // Gerätelokale TTS-Config: globaler Toggle (aus Settings) + temporäres Muten (Mund-Button)
@@ -1591,7 +1591,7 @@ const ChatScreen: React.FC = () => {
} }
} }
// Gamebox-Bridges (f5tts/whisper/flux) melden Lade-Status — Banner oben. // AI-Box-Bridges (f5tts/whisper/flux) melden Lade-Status — Banner oben.
// Toast bei Download-Ende: erstmaliger HF-Download (mehrere GB) → User // Toast bei Download-Ende: erstmaliger HF-Download (mehrere GB) → User
// soll wissen dass er Bilder/Stimmen jetzt nutzen kann ohne in den // soll wissen dass er Bilder/Stimmen jetzt nutzen kann ohne in den
// Banner gucken zu muessen. // Banner gucken zu muessen.
@@ -2289,7 +2289,7 @@ const ChatScreen: React.FC = () => {
// Aufraeumen von "verarbeitet"-Placeholder die nie ein STT-Result bekommen // Aufraeumen von "verarbeitet"-Placeholder die nie ein STT-Result bekommen
// haben (leere Aufnahme, Wake-Word-Echo, STT-Fehler etc). Timeout skaliert // haben (leere Aufnahme, Wake-Word-Echo, STT-Fehler etc). Timeout skaliert
// mit der Aufnahmedauer — Whisper braucht auf der Gamebox grob real-time/5, // mit der Aufnahmedauer — Whisper braucht auf der AI-Box grob real-time/5,
// plus Bridge-Roundtrip + Network. Formel: 60s Buffer + 1x Aufnahmedauer. // plus Bridge-Roundtrip + Network. Formel: 60s Buffer + 1x Aufnahmedauer.
// Bei 5min Aufnahme = 6 min Wait, bei 5s Aufnahme = 65s. Sicher genug damit // Bei 5min Aufnahme = 6 min Wait, bei 5s Aufnahme = 65s. Sicher genug damit
// langsame STTs nicht versehentlich aufgeraeumt werden. // langsame STTs nicht versehentlich aufgeraeumt werden.
@@ -3060,7 +3060,7 @@ const ChatScreen: React.FC = () => {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
{/* Service-Status Banner (Gamebox: F5-TTS / Whisper Lade-Status) */} {/* Service-Status Banner (AI-Box: F5-TTS / Whisper Lade-Status) */}
{(() => { {(() => {
const entries = Object.entries(serviceStatus); const entries = Object.entries(serviceStatus);
if (entries.length === 0 || serviceBannerDismissed) return null; if (entries.length === 0 || serviceBannerDismissed) return null;
+1 -1
View File
@@ -859,7 +859,7 @@ META_TOOLS = [
"function": { "function": {
"name": "flux_generate", "name": "flux_generate",
"description": ( "description": (
"Generiere ein Bild aus einem Text-Prompt via FLUX auf der Gamebox-GPU. " "Generiere ein Bild aus einem Text-Prompt via FLUX auf der AI-Box-GPU. "
"Brauchbar fuer 'mal mir ein X', 'wie sieht ein Y aus?', Mockups, " "Brauchbar fuer 'mal mir ein X', 'wie sieht ein Y aus?', Mockups, "
"Konzept-Skizzen, Memes. Render dauert 20-90s — kuendige es Stefan " "Konzept-Skizzen, Memes. Render dauert 20-90s — kuendige es Stefan "
"kurz an, dann ist er nicht ueberrascht.\n\n" "kurz an, dann ist er nicht ueberrascht.\n\n"
+1 -1
View File
@@ -1,7 +1,7 @@
""" """
Local-LLM-Client (Plan B) — Brain-Seite. Local-LLM-Client (Plan B) — Brain-Seite.
Ruft das schnelle lokale LLM (Qwen3 auf der Gamebox) ueber die Bridge: Ruft das schnelle lokale LLM (Qwen3 auf der AI-Box) ueber die Bridge:
Brain → HTTP /internal/local-llm → Bridge → RVS → llm-adapter → llama.cpp Brain → HTTP /internal/local-llm → Bridge → RVS → llm-adapter → llama.cpp
Analog zum Claude-`proxy_client`, nur ueber die Bridge (die ist der RVS-Client; Analog zum Claude-`proxy_client`, nur ueber die Bridge (die ist der RVS-Client;
+22 -22
View File
@@ -2,7 +2,7 @@
ARIA Voice Bridge — Hauptmodul. ARIA Voice Bridge — Hauptmodul.
Verbindet die Android App (via RVS) mit ARIA-Core. Spracheingabe laeuft Verbindet die Android App (via RVS) mit ARIA-Core. Spracheingabe laeuft
ueber die whisper-bridge (Gamebox, faster-whisper auf CUDA), Sprachausgabe ueber die whisper-bridge (AI-Box, faster-whisper auf CUDA), Sprachausgabe
ueber die f5tts-bridge (Voice Cloning, satzweises PCM-Streaming). ueber die f5tts-bridge (Voice Cloning, satzweises PCM-Streaming).
Nachrichtenfluss: Nachrichtenfluss:
@@ -479,7 +479,7 @@ class STTEngine:
Erkannter Text oder leerer String. Erkannter Text oder leerer String.
""" """
if self.model is None: if self.model is None:
# Lazy-Load: normalerweise laeuft STT remote auf der Gamebox. # Lazy-Load: normalerweise laeuft STT remote auf der AI-Box.
# Erst wenn das Fallback hier zuschlaegt, laden wir lokal. # Erst wenn das Fallback hier zuschlaegt, laden wir lokal.
logger.info("Lokales Whisper-Fallback — Modell wird nachgeladen...") logger.info("Lokales Whisper-Fallback — Modell wird nachgeladen...")
try: try:
@@ -654,7 +654,7 @@ class ARIABridge:
self._seen_client_msg_ids: "OrderedDict[str, float]" = OrderedDict() self._seen_client_msg_ids: "OrderedDict[str, float]" = OrderedDict()
self._SEEN_CLIENT_MSG_LIMIT = 200 self._SEEN_CLIENT_MSG_LIMIT = 200
# Komponenten (TTS: F5-TTS remote auf der Gamebox, lokales TTS wurde entfernt) # Komponenten (TTS: F5-TTS remote auf der AI-Box, lokales TTS wurde entfernt)
self.tts_enabled = True self.tts_enabled = True
self.xtts_voice = "" self.xtts_voice = ""
self._f5tts_config: dict = {} self._f5tts_config: dict = {}
@@ -686,7 +686,7 @@ class ARIABridge:
except (TypeError, ValueError): except (TypeError, ValueError):
self._persistent_xtts_speed = None self._persistent_xtts_speed = None
# F5-TTS-Felder aufsammeln (werden spaeter via RVS rebroadcastet, # F5-TTS-Felder aufsammeln (werden spaeter via RVS rebroadcastet,
# damit die f5tts-bridge auf der Gamebox die Settings auch nach # damit die f5tts-bridge auf der AI-Box die Settings auch nach
# Restart wiederbekommt — sonst stuende sie auf Hard-Defaults) # Restart wiederbekommt — sonst stuende sie auf Hard-Defaults)
for k in ("f5ttsModel", "f5ttsCkptFile", "f5ttsVocabFile", for k in ("f5ttsModel", "f5ttsCkptFile", "f5ttsVocabFile",
"f5ttsCfgStrength", "f5ttsNfeStep"): "f5ttsCfgStrength", "f5ttsNfeStep"):
@@ -746,7 +746,7 @@ class ARIABridge:
# Gleiche Logik fuer die Wiedergabegeschwindigkeit (F5-TTS speed-Param, # Gleiche Logik fuer die Wiedergabegeschwindigkeit (F5-TTS speed-Param,
# App-Setting aria_tts_speed, 1.0 = normal). # App-Setting aria_tts_speed, 1.0 = normal).
self._next_speed_override: Optional[float] = None self._next_speed_override: Optional[float] = None
# STT-Requests die aktuell auf Antwort von der whisper-bridge (Gamebox) warten. # STT-Requests die aktuell auf Antwort von der whisper-bridge (AI-Box) warten.
# requestId → Future mit dem Text (oder None bei Fehler). # requestId → Future mit dem Text (oder None bei Fehler).
self._pending_stt: dict[str, asyncio.Future] = {} self._pending_stt: dict[str, asyncio.Future] = {}
# whisper-bridge service_status: True wenn ready, False/None wenn loading/unbekannt. # whisper-bridge service_status: True wenn ready, False/None wenn loading/unbekannt.
@@ -771,7 +771,7 @@ class ARIABridge:
# {service, node, gpus, model, busy, last_seen}. Genutzt fuer die # {service, node, gpus, model, busy, last_seen}. Genutzt fuer die
# Diagnostic-Flotten-Anzeige und (Stage 3) targetInstance-Routing. # Diagnostic-Flotten-Anzeige und (Stage 3) targetInstance-Routing.
self._workers: dict[str, dict] = {} self._workers: dict[str, dict] = {}
# FLUX-Render-Requests die aktuell auf Antwort der flux-bridge (Gamebox) warten. # FLUX-Render-Requests die aktuell auf Antwort der flux-bridge (AI-Box) warten.
# requestId → Future mit dem flux_response-Payload (oder None bei Fehler). # requestId → Future mit dem flux_response-Payload (oder None bei Fehler).
self._pending_flux: dict[str, asyncio.Future] = {} self._pending_flux: dict[str, asyncio.Future] = {}
# flux-bridge service_status: True wenn ready. Render-Timeouts werden # flux-bridge service_status: True wenn ready. Render-Timeouts werden
@@ -779,7 +779,7 @@ class ARIABridge:
self._remote_flux_ready: bool = False self._remote_flux_ready: bool = False
# Lokales LLM (Plan B): requestId → Future mit dem llm_response-Payload. # Lokales LLM (Plan B): requestId → Future mit dem llm_response-Payload.
# Analog zu _pending_flux — Brain ruft /internal/local-llm, wir relayen # Analog zu _pending_flux — Brain ruft /internal/local-llm, wir relayen
# llm_request via RVS an den llm-adapter (Gamebox) und warten auf # llm_request via RVS an den llm-adapter (AI-Box) und warten auf
# llm_response. # llm_response.
self._pending_llm: dict[str, asyncio.Future] = {} self._pending_llm: dict[str, asyncio.Future] = {}
# User-Message-Counter fuer Auto-Compact. Bei zu langer Konversation # User-Message-Counter fuer Auto-Compact. Bei zu langer Konversation
@@ -815,7 +815,7 @@ class ARIABridge:
logger.info("ARIA Voice Bridge startet...") logger.info("ARIA Voice Bridge startet...")
logger.info("=" * 50) logger.info("=" * 50)
# STT wird standardmaessig von der whisper-bridge (Gamebox) erledigt. # STT wird standardmaessig von der whisper-bridge (AI-Box) erledigt.
# Lokales Whisper ist nur Fallback und wird lazy geladen wenn remote nicht # Lokales Whisper ist nur Fallback und wird lazy geladen wenn remote nicht
# antwortet. Das spart RAM auf der VM und Startup-Zeit. # antwortet. Das spart RAM auf der VM und Startup-Zeit.
@@ -1759,7 +1759,7 @@ class ARIABridge:
"""Broadcastet die aktuelle voice_config.json einmalig nach RVS-Connect. """Broadcastet die aktuelle voice_config.json einmalig nach RVS-Connect.
Damit bekommen frisch verbundene Bridges (insbesondere die f5tts-bridge Damit bekommen frisch verbundene Bridges (insbesondere die f5tts-bridge
auf der Gamebox nach Container-Restart) die zuletzt in Diagnostic auf der AI-Box nach Container-Restart) die zuletzt in Diagnostic
gewaehlten Settings — ohne dass der User in Diagnostic was klicken muss. gewaehlten Settings — ohne dass der User in Diagnostic was klicken muss.
""" """
try: try:
@@ -2202,7 +2202,7 @@ class ARIABridge:
await self._broadcast_current_mode() await self._broadcast_current_mode()
# Persistierte Voice-Config broadcasten — die f5tts-bridge auf # Persistierte Voice-Config broadcasten — die f5tts-bridge auf
# der Gamebox bekommt damit nach Restart die zuletzt in # der AI-Box bekommt damit nach Restart die zuletzt in
# Diagnostic gewaehlten Settings wieder (sonst stuende sie auf # Diagnostic gewaehlten Settings wieder (sonst stuende sie auf
# ihren Hard-Defaults). # ihren Hard-Defaults).
asyncio.create_task(self._broadcast_persisted_config()) asyncio.create_task(self._broadcast_persisted_config())
@@ -2563,7 +2563,7 @@ class ARIABridge:
elif msg_type == "config": elif msg_type == "config":
# Konfiguration von App/Diagnostic empfangen + persistent speichern. # Konfiguration von App/Diagnostic empfangen + persistent speichern.
# Felder die nicht direkt zur aria-bridge gehoeren (f5tts*) werden # Felder die nicht direkt zur aria-bridge gehoeren (f5tts*) werden
# nur persistiert; die f5tts-bridge auf der Gamebox empfaengt den # nur persistiert; die f5tts-bridge auf der AI-Box empfaengt den
# gleichen RVS-Broadcast und reagiert selber. # gleichen RVS-Broadcast und reagiert selber.
changed = False changed = False
if "ttsEnabled" in payload: if "ttsEnabled" in payload:
@@ -2587,7 +2587,7 @@ class ARIABridge:
new_model = payload["whisperModel"] new_model = payload["whisperModel"]
allowed = {"tiny", "base", "small", "medium", "large-v3"} allowed = {"tiny", "base", "small", "medium", "large-v3"}
if new_model in allowed and new_model != self.stt_engine.model_size: if new_model in allowed and new_model != self.stt_engine.model_size:
logger.info("[rvs] Whisper-Modell → %s (nur Config; Modell laedt Gamebox)", logger.info("[rvs] Whisper-Modell → %s (nur Config; Modell laedt AI-Box)",
new_model) new_model)
self.stt_engine.model_size = new_model self.stt_engine.model_size = new_model
self.stt_engine.model = None self.stt_engine.model = None
@@ -3448,7 +3448,7 @@ class ARIABridge:
return return
elif msg_type == "llm_response": elif msg_type == "llm_response":
# Antwort des llm-adapter (Gamebox) auf unseren llm_request. # Antwort des llm-adapter (AI-Box) auf unseren llm_request.
request_id = payload.get("requestId", "") request_id = payload.get("requestId", "")
future = self._pending_llm.get(request_id) future = self._pending_llm.get(request_id)
if future is None or future.done(): if future is None or future.done():
@@ -3457,7 +3457,7 @@ class ARIABridge:
return return
elif msg_type == "service_status": elif msg_type == "service_status":
# Gamebox-Bridges (whisper / f5tts / flux) melden ihren Lade-Status. # AI-Box-Bridges (whisper / f5tts / flux) melden ihren Lade-Status.
# Wir nutzen das fuer den dynamischen STT-Timeout: solange whisper # Wir nutzen das fuer den dynamischen STT-Timeout: solange whisper
# im 'loading' steckt, geben wir der Bridge mehr Zeit (Modell-Download # im 'loading' steckt, geben wir der Bridge mehr Zeit (Modell-Download
# kann 1-2 Min dauern), statt nach 45s lokal zu fallbacken. # kann 1-2 Min dauern), statt nach 45s lokal zu fallbacken.
@@ -3616,11 +3616,11 @@ class ARIABridge:
else: else:
logger.debug("[rvs] Unbekannter Typ: %s", msg_type) logger.debug("[rvs] Unbekannter Typ: %s", msg_type)
# STT-Orchestrierung: zuerst Remote (Gamebox), Fallback lokal. # STT-Orchestrierung: zuerst Remote (AI-Box), Fallback lokal.
# Zwei Timeouts: # Zwei Timeouts:
# ready=True → 45s reicht selbst fuer lange Audios # ready=True → 45s reicht selbst fuer lange Audios
# ready=False → 300s, weil das Modell evtl. noch heruntergeladen wird # ready=False → 300s, weil das Modell evtl. noch heruntergeladen wird
# (large-v3 ~3GB, kann auf der Gamebox 1-2 Min dauern). # (large-v3 ~3GB, kann auf der AI-Box 1-2 Min dauern).
_STT_REMOTE_TIMEOUT_READY_S = 45.0 _STT_REMOTE_TIMEOUT_READY_S = 45.0
_STT_REMOTE_TIMEOUT_LOADING_S = 300.0 _STT_REMOTE_TIMEOUT_LOADING_S = 300.0
@@ -3973,15 +3973,15 @@ class ARIABridge:
_FLUX_TIMEOUT_LOADING_S = 900.0 # 15 min beim allerersten Mal (Modell-Download) _FLUX_TIMEOUT_LOADING_S = 900.0 # 15 min beim allerersten Mal (Modell-Download)
# ── Local-LLM-Roundtrip: Brain → Bridge → RVS → llm-adapter → zurueck ── # ── Local-LLM-Roundtrip: Brain → Bridge → RVS → llm-adapter → zurueck ──
# Qwen3 auf der Gamebox antwortet auf kurze Turns in <1 s. Grosszuegiger # Qwen3 auf der AI-Box antwortet auf kurze Turns in <1 s. Grosszuegiger
# Timeout deckt Kaltstart / laengere Antworten / Netz-Jitter (Gamebox@home) # Timeout deckt Kaltstart / laengere Antworten / Netz-Jitter (AI-Box@home)
# ab. Bei Timeout faellt der Router im Brain per Escalation auf Claude. # ab. Bei Timeout faellt der Router im Brain per Escalation auf Claude.
_LLM_TIMEOUT_S = 30.0 _LLM_TIMEOUT_S = 30.0
async def _local_llm(self, messages: list, max_tokens: int = 512, async def _local_llm(self, messages: list, max_tokens: int = 512,
temperature: float = 0.7, stop=None, tools=None, temperature: float = 0.7, stop=None, tools=None,
model=None) -> dict: model=None) -> dict:
"""Schickt einen llm_request an den llm-adapter (Gamebox), wartet auf """Schickt einen llm_request an den llm-adapter (AI-Box), wartet auf
llm_response. tools (B1b) werden durchgereicht; tool_calls kommen zurueck. llm_response. tools (B1b) werden durchgereicht; tool_calls kommen zurueck.
Rueckgabe: {ok, content, tool_calls, model, elapsedMs} oder {ok:False, error}.""" Rueckgabe: {ok, content, tool_calls, model, elapsedMs} oder {ok:False, error}."""
if self.ws_rvs is None: if self.ws_rvs is None:
@@ -4024,7 +4024,7 @@ class ARIABridge:
try: try:
result = await asyncio.wait_for(future, timeout=self._LLM_TIMEOUT_S) result = await asyncio.wait_for(future, timeout=self._LLM_TIMEOUT_S)
except asyncio.TimeoutError: except asyncio.TimeoutError:
return {"ok": False, "error": f"Timeout ({self._LLM_TIMEOUT_S:.0f}s) — Gamebox nicht erreichbar?"} return {"ok": False, "error": f"Timeout ({self._LLM_TIMEOUT_S:.0f}s) — AI-Box nicht erreichbar?"}
if not isinstance(result, dict) or not result.get("ok"): if not isinstance(result, dict) or not result.get("ok"):
err = (result or {}).get("error") if isinstance(result, dict) else "leeres Resultat" err = (result or {}).get("error") if isinstance(result, dict) else "leeres Resultat"
return {"ok": False, "error": err or "llm-adapter Fehler"} return {"ok": False, "error": err or "llm-adapter Fehler"}
@@ -4527,7 +4527,7 @@ class ARIABridge:
await _send_response(writer, 200, result) await _send_response(writer, 200, result)
elif method == "POST" and path == "/internal/flux-generate": elif method == "POST" and path == "/internal/flux-generate":
# Vom Brain (flux_generate-Tool) gefeuert. Wir routen den # Vom Brain (flux_generate-Tool) gefeuert. Wir routen den
# Render-Request via RVS an die flux-bridge (Gamebox), # Render-Request via RVS an die flux-bridge (AI-Box),
# warten synchron auf die PNG-Antwort, speichern sie nach # warten synchron auf die PNG-Antwort, speichern sie nach
# /shared/uploads/ und melden Pfad + Render-Stats zurueck. # /shared/uploads/ und melden Pfad + Render-Stats zurueck.
# Brain referenziert das Bild dann mit [FILE:]-Marker in # Brain referenziert das Bild dann mit [FILE:]-Marker in
@@ -4564,7 +4564,7 @@ class ARIABridge:
await _send_response(writer, status, result) await _send_response(writer, status, result)
elif method == "POST" and path == "/internal/local-llm": elif method == "POST" and path == "/internal/local-llm":
# Vom Brain (Router / Testchat) gefeuert. Wir relayen den # Vom Brain (Router / Testchat) gefeuert. Wir relayen den
# Chat-Request via RVS an den llm-adapter (Gamebox Qwen3), # Chat-Request via RVS an den llm-adapter (AI-Box Qwen3),
# warten synchron auf llm_response und geben content zurueck. # warten synchron auf llm_response und geben content zurueck.
try: try:
data = json.loads(body.decode("utf-8", "ignore")) data = json.loads(body.decode("utf-8", "ignore"))
+9 -9
View File
@@ -162,7 +162,7 @@
</style> </style>
</head> </head>
<body> <body>
<!-- Service-Status Banner unten rechts (Gamebox: F5-TTS / Whisper Lade-Status) --> <!-- Service-Status Banner unten rechts (AI-Box: F5-TTS / Whisper Lade-Status) -->
<div id="service-status-banner" style="display:none;position:fixed;bottom:16px;right:16px;z-index:999;background:#1E1E2E;border:1px solid #2A2A3E;border-radius:8px;padding:10px 14px;font-size:12px;color:#fff;min-width:240px;max-width:360px;box-shadow:0 4px 14px rgba(0,0,0,0.5);"> <div id="service-status-banner" style="display:none;position:fixed;bottom:16px;right:16px;z-index:999;background:#1E1E2E;border:1px solid #2A2A3E;border-radius:8px;padding:10px 14px;font-size:12px;color:#fff;min-width:240px;max-width:360px;box-shadow:0 4px 14px rgba(0,0,0,0.5);">
<div style="display:flex;align-items:flex-start;gap:8px;"> <div style="display:flex;align-items:flex-start;gap:8px;">
<span id="service-status-icon" style="font-size:18px;line-height:1;">&#x23F3;</span> <span id="service-status-icon" style="font-size:18px;line-height:1;">&#x23F3;</span>
@@ -548,7 +548,7 @@
<h2>Lokales LLM (schnelle Antworten) <button class="info-btn" onclick="showInfo('local-llm')" title="Wie funktioniert das?">ℹ</button></h2> <h2>Lokales LLM (schnelle Antworten) <button class="info-btn" onclick="showInfo('local-llm')" title="Wie funktioniert das?">ℹ</button></h2>
<div class="card" style="max-width:540px;"> <div class="card" style="max-width:540px;">
<div style="font-size:11px;color:#8888AA;margin-bottom:12px;line-height:1.55;"> <div style="font-size:11px;color:#8888AA;margin-bottom:12px;line-height:1.55;">
Ein schnelles lokales Modell (<strong>Qwen3</strong> auf der Gamebox) beantwortet Ein schnelles lokales Modell (<strong>Qwen3</strong> auf der AI-Box) beantwortet
<strong>leichte Fragen in unter 1&nbsp;Sekunde</strong>. Was schwerer wird, gibt es <strong>leichte Fragen in unter 1&nbsp;Sekunde</strong>. Was schwerer wird, gibt es
automatisch an <strong>Claude</strong> ab. Aenderungen greifen sofort automatisch an <strong>Claude</strong> ab. Aenderungen greifen sofort
(kein Neustart) — geschrieben nach <code>/shared/config/local_llm.json</code>. (kein Neustart) — geschrieben nach <code>/shared/config/local_llm.json</code>.
@@ -600,7 +600,7 @@
</div> </div>
<div id="local-llm-model-desc" style="font-size:10px;color:#8888AA;margin:0 0 2px 0;line-height:1.5;"></div> <div id="local-llm-model-desc" style="font-size:10px;color:#8888AA;margin:0 0 2px 0;line-height:1.5;"></div>
<div style="font-size:10px;color:#FFD60A;margin:0 0 4px 0;line-height:1.5;"> <div style="font-size:10px;color:#FFD60A;margin:0 0 4px 0;line-height:1.5;">
Beim ersten Wechsel zu einem Modell lädt die Gamebox das GGUF (mehrere GB) — Beim ersten Wechsel zu einem Modell lädt die AI-Box das GGUF (mehrere GB) —
die <strong>erste Antwort dauert dann länger</strong>, danach ist es gecacht. die <strong>erste Antwort dauert dann länger</strong>, danach ist es gecacht.
Liste kommt aus <code>/shared/config/local_models.json</code> (Keys = xtts/llama-swap/config.yaml). Liste kommt aus <code>/shared/config/local_models.json</code> (Keys = xtts/llama-swap/config.yaml).
</div> </div>
@@ -647,7 +647,7 @@
<summary style="color:#8888AA;font-size:12px;cursor:pointer;">F5-TTS Modell-Tuning (advanced)</summary> <summary style="color:#8888AA;font-size:12px;cursor:pointer;">F5-TTS Modell-Tuning (advanced)</summary>
<div style="margin-top:10px;display:flex;flex-direction:column;gap:8px;"> <div style="margin-top:10px;display:flex;flex-direction:column;gap:8px;">
<div style="color:#8888AA;font-size:11px;"> <div style="color:#8888AA;font-size:11px;">
Werden via RVS an die f5tts-bridge auf der Gamebox geschickt. Werden via RVS an die f5tts-bridge auf der AI-Box geschickt.
Modell-/Checkpoint-Wechsel triggert einen Reload (~30s). Modell-/Checkpoint-Wechsel triggert einen Reload (~30s).
Hardcoded Defaults: F5TTS_v1_Base, cfg_strength=2.5, nfe_step=32. Hardcoded Defaults: F5TTS_v1_Base, cfg_strength=2.5, nfe_step=32.
</div> </div>
@@ -838,7 +838,7 @@
<div class="settings-section"> <div class="settings-section">
<h2>FLUX Bildgenerierung</h2> <h2>FLUX Bildgenerierung</h2>
<div style="font-size:11px;color:#8888AA;margin-bottom:8px;"> <div style="font-size:11px;color:#8888AA;margin-bottom:8px;">
Steuerung der Image-Generation (flux-bridge auf der Gamebox). Steuerung der Image-Generation (flux-bridge auf der AI-Box).
Default-Modell wird via RVS gepusht — Wechsel triggert Pipeline-Reload (15-30s Default-Modell wird via RVS gepusht — Wechsel triggert Pipeline-Reload (15-30s
aus HF-Cache, mehrere Minuten beim Erst-Download). Keywords nutzt ARIAs Brain aus HF-Cache, mehrere Minuten beim Erst-Download). Keywords nutzt ARIAs Brain
im System-Prompt. im System-Prompt.
@@ -2939,7 +2939,7 @@
'Glob': '\uD83D\uDCC1 Dateien suchen', 'Glob': '\uD83D\uDCC1 Dateien suchen',
'Agent': '\uD83E\uDD16 Sub-Agent', 'Agent': '\uD83E\uDD16 Sub-Agent',
}; };
// ── Service-Status Banner (Gamebox: F5-TTS / Whisper Lade-Status) ── // ── Service-Status Banner (AI-Box: F5-TTS / Whisper Lade-Status) ──
// Aggregiert die Status-Infos der Bridges. Wenn irgendwas am Laden // Aggregiert die Status-Infos der Bridges. Wenn irgendwas am Laden
// ist, zeigt das Banner unten rechts. Sobald alles auf 'ready' ist, // ist, zeigt das Banner unten rechts. Sobald alles auf 'ready' ist,
// bleibt's einen Moment und wird dann vom User weggeklickt (oder // bleibt's einen Moment und wird dann vom User weggeklickt (oder
@@ -6475,17 +6475,17 @@
html: ` html: `
<p>Jeder Turn, den das <strong>lokale LLM</strong> oder ein <strong>Fast-Path</strong> (reiner Skill, ganz ohne LLM) beantwortet, ist ein Claude-Call, der <strong>nicht</strong> gegen dein Subscription-Quota laeuft.</p> <p>Jeder Turn, den das <strong>lokale LLM</strong> oder ein <strong>Fast-Path</strong> (reiner Skill, ganz ohne LLM) beantwortet, ist ein Claude-Call, der <strong>nicht</strong> gegen dein Subscription-Quota laeuft.</p>
<p><strong>„Claude-Calls gespart"</strong> = Anzahl der local- + fast-path-Antworten im Zeitfenster. Konservativ gezaehlt: 1 Antwort = mindestens 1 gesparter Claude-Call (bei Tool-Use waeren es real oft mehr).</p> <p><strong>„Claude-Calls gespart"</strong> = Anzahl der local- + fast-path-Antworten im Zeitfenster. Konservativ gezaehlt: 1 Antwort = mindestens 1 gesparter Claude-Call (bei Tool-Use waeren es real oft mehr).</p>
<p><strong>„lokale Tokens"</strong> = Prompt+Antwort-Tokens, die auf deiner eigenen Gamebox-GPU verarbeitet wurden (echte <code>usage</code>-Zahlen vom Modell, sonst chars/4-Schaetzung). Die kosten dich nichts ausser Strom.</p> <p><strong>„lokale Tokens"</strong> = Prompt+Antwort-Tokens, die auf deiner eigenen AI-Box-GPU verarbeitet wurden (echte <code>usage</code>-Zahlen vom Modell, sonst chars/4-Schaetzung). Die kosten dich nichts ausser Strom.</p>
<p>Fast-Path-Antworten (z.B. „nächstes Lied") haben ~0 Tokens — reiner Skill-Aufruf, kein Modell.</p> <p>Fast-Path-Antworten (z.B. „nächstes Lied") haben ~0 Tokens — reiner Skill-Aufruf, kein Modell.</p>
`, `,
}, },
'local-llm': { 'local-llm': {
title: 'Lokales LLM — schnelle Antworten', title: 'Lokales LLM — schnelle Antworten',
html: ` html: `
<p>Ein kleines, schnelles Modell (<strong>Qwen3 8B</strong>) laeuft auf deiner Gamebox-GPU und beantwortet <strong>einfache Plauder-Turns in &lt;1&nbsp;s</strong>. Alles Schwere, Technische oder Werkzeug-artige (Wetter, Timer, Musik, Bilder, Gedaechtnis, Code) reicht ein Router automatisch an <strong>Claude</strong> weiter.</p> <p>Ein kleines, schnelles Modell (<strong>Qwen3 8B</strong>) laeuft auf deiner AI-Box-GPU und beantwortet <strong>einfache Plauder-Turns in &lt;1&nbsp;s</strong>. Alles Schwere, Technische oder Werkzeug-artige (Wetter, Timer, Musik, Bilder, Gedaechtnis, Code) reicht ein Router automatisch an <strong>Claude</strong> weiter.</p>
<p><strong>Lokales LLM nutzen</strong> — Master-Schalter. Aus = alle Anfragen laufen wie bisher ueber Claude.</p> <p><strong>Lokales LLM nutzen</strong> — Master-Schalter. Aus = alle Anfragen laufen wie bisher ueber Claude.</p>
<p><strong>Nur lokales LLM</strong> — erzwingt lokal, KEIN Claude-Fallback. Zum Ausprobieren, wie stark das lokale Modell allein ist. Achtung: Werkzeug-Turns (Wetter/Timer/…) funktionieren dann nicht — das lokale Tier hat keine Tools.</p> <p><strong>Nur lokales LLM</strong> — erzwingt lokal, KEIN Claude-Fallback. Zum Ausprobieren, wie stark das lokale Modell allein ist. Achtung: Werkzeug-Turns (Wetter/Timer/…) funktionieren dann nicht — das lokale Tier hat keine Tools.</p>
<p>Die Antwortzeit haengt an deinem Heim-Internet (Gamebox @home, ARIA @RZ). Ist die Gamebox aus/nicht erreichbar, faellt ARIA automatisch auf Claude zurueck.</p> <p>Die Antwortzeit haengt an deinem Heim-Internet (AI-Box @home, ARIA @RZ). Ist die AI-Box aus/nicht erreichbar, faellt ARIA automatisch auf Claude zurueck.</p>
`, `,
}, },
'local-llm-tools': { 'local-llm-tools': {
+1 -1
View File
@@ -1025,7 +1025,7 @@ function connectRVS(forcePlain) {
} }
broadcast({ type: "voice_ready", payload: msg.payload }); broadcast({ type: "voice_ready", payload: msg.payload });
} else if (msg.type === "service_status") { } else if (msg.type === "service_status") {
// Gamebox-Bridges (f5tts/whisper) melden ihren Lade-Status — // AI-Box-Bridges (f5tts/whisper) melden ihren Lade-Status —
// an Browser durchreichen fuer das Banner unten rechts // an Browser durchreichen fuer das Banner unten rechts
const svc = msg.payload?.service || "?"; const svc = msg.payload?.service || "?";
const state = msg.payload?.state || "?"; const state = msg.payload?.state || "?";
+2 -2
View File
@@ -1,7 +1,7 @@
# FLUX.1-dev Bildgenerierung — Architektur & Stand # FLUX.1-dev Bildgenerierung — Architektur & Stand
Ergaenzung des ARIA-Agent-Stacks um native Text-to-Image-Generierung via Ergaenzung des ARIA-Agent-Stacks um native Text-to-Image-Generierung via
FLUX.1-dev auf der Gamebox. Folgt dem **gleichen Pattern wie f5tts / whisper**: FLUX.1-dev auf der AI-Box. Folgt dem **gleichen Pattern wie f5tts / whisper**:
ein eigener Container auf dem Gaming-PC, der sich selbst per WebSocket zum ein eigener Container auf dem Gaming-PC, der sich selbst per WebSocket zum
RVS verbindet und auf seinen Request-Typ lauscht. RVS verbindet und auf seinen Request-Typ lauscht.
@@ -23,7 +23,7 @@ aria-bridge ── send_to_core ──▶ aria-brain
RVS RVS
│ fanout │ fanout
▼ ▼
flux-bridge (Gamebox) flux-bridge (AI-Box)
│ FluxPipeline.from_pretrained(...) │ FluxPipeline.from_pretrained(...)
│ pipeline(prompt, width, height, steps, guidance).images[0] │ pipeline(prompt, width, height, steps, guidance).images[0]
│ PIL → PNG → base64 │ PIL → PNG → base64
+9 -9
View File
@@ -1,4 +1,4 @@
# Plan B — Lokaler LLM-Router (Gamebox) neben Claude # Plan B — Lokaler LLM-Router (AI-Box) neben Claude
**Ziel:** „Gemini-Feeling" für den Alltag, ohne die Claude-Max-Subscription **Ziel:** „Gemini-Feeling" für den Alltag, ohne die Claude-Max-Subscription
aufzugeben. Ein schnelles lokales LLM beantwortet die einfachen ~80 % der Turns aufzugeben. Ein schnelles lokales LLM beantwortet die einfachen ~80 % der Turns
@@ -11,7 +11,7 @@ Gemessen (10.07.2026): CLI-Round-trip über den Claude-Max-Proxy hat einen
**harten Boden von ~3,5 s** (Subprozess-Start pro Turn). Streaming-API würde das **harten Boden von ~3,5 s** (Subprozess-Start pro Turn). Streaming-API würde das
brechen, kostet aber API-Geld → verliert die Max-Subscription. Ein lokales brechen, kostet aber API-Geld → verliert die Max-Subscription. Ein lokales
LLM für die einfachen Turns umgeht den 3,5-s-Boden komplett und ist **gratis** LLM für die einfachen Turns umgeht den 3,5-s-Boden komplett und ist **gratis**
(läuft auf vorhandener Gamebox-GPU). Echtes Speech-to-Speech-Duplex (Gemini (läuft auf vorhandener AI-Box-GPU). Echtes Speech-to-Speech-Duplex (Gemini
Live nativ) ist mit einem Text-Modell als Hirn prinzipiell nicht drin. Live nativ) ist mit einem Text-Modell als Hirn prinzipiell nicht drin.
## Modell & Serving (entschieden) ## Modell & Serving (entschieden)
@@ -19,17 +19,17 @@ Live nativ) ist mit einem Text-Modell als Hirn prinzipiell nicht drin.
- **Modell:** Qwen3 8B, GGUF **Q4_K_M** (~6 GB). Bestes Tool-Calling der 7/8B- - **Modell:** Qwen3 8B, GGUF **Q4_K_M** (~6 GB). Bestes Tool-Calling der 7/8B-
Klasse, solides Deutsch, Apache-2.0. Alt.: Mistral Small 3 7B (schneller, Klasse, solides Deutsch, Apache-2.0. Alt.: Mistral Small 3 7B (schneller,
weniger Tool-Calling). weniger Tool-Calling).
- **Serving:** **llama.cpp `llama-server`** im Docker-Container auf der Gamebox - **Serving:** **llama.cpp `llama-server`** im Docker-Container auf der AI-Box
(kein Ollama nötig — nativer OpenAI-kompatibler `/v1/chat/completions`). (kein Ollama nötig — nativer OpenAI-kompatibler `/v1/chat/completions`).
- **VRAM-Budget:** 12-GB-Karte, Whisper-small (~1–2 GB) + F5-TTS (~1–2 GB) → - **VRAM-Budget:** 12-GB-Karte, Whisper-small (~1–2 GB) + F5-TTS (~1–2 GB) →
~8–9 GB frei → passt. (FLUX ist auf 12 GB eh raus.) ~8–9 GB frei → passt. (FLUX ist auf 12 GB eh raus.)
## Anbindung: über den RVS, wie TTS/STT (kein IP-Pflegen) ## Anbindung: über den RVS, wie TTS/STT (kein IP-Pflegen)
Die Gamebox ist ein anderer Host als das Brain. Statt direktem HTTP (IP/Port/ Die AI-Box ist ein anderer Host als das Brain. Statt direktem HTTP (IP/Port/
Firewall) läuft das LLM **über den RVS-Token-Room**, exakt wie Whisper/F5-TTS: Firewall) läuft das LLM **über den RVS-Token-Room**, exakt wie Whisper/F5-TTS:
- llama.cpp hört nur auf localhost der Gamebox. - llama.cpp hört nur auf localhost der AI-Box.
- Ein **dünner RVS-Adapter** daneben (Vorbild: whisper-/xtts-Bridge) verbindet - Ein **dünner RVS-Adapter** daneben (Vorbild: whisper-/xtts-Bridge) verbindet
sich mit dem RVS-Token, lauscht auf `llm_request`, ruft lokal llama-server, sich mit dem RVS-Token, lauscht auf `llm_request`, ruft lokal llama-server,
schickt `llm_response` (korreliert per requestId) zurück. schickt `llm_response` (korreliert per requestId) zurück.
@@ -115,7 +115,7 @@ mit Ziel lokal.
## Phasen ## Phasen
- **B0 — Infra:** llama.cpp-Container + RVS-Adapter auf der Gamebox, - **B0 — Infra:** llama.cpp-Container + RVS-Adapter auf der AI-Box,
`ALLOWED_TYPES`, `local_llm_chat()` im Brain. Isoliert testen („sag hallo"). `ALLOWED_TYPES`, `local_llm_chat()` im Brain. Isoliert testen („sag hallo").
- **B1 — Router + lokale Tools:** Heuristik Tier-1/2 + Escalation, schlanke - **B1 — Router + lokale Tools:** Heuristik Tier-1/2 + Escalation, schlanke
Persona lokal, **kuratierte Tool-Auswahl lokal** (Adapter/Bridge/Brain-Tool- Persona lokal, **kuratierte Tool-Auswahl lokal** (Adapter/Bridge/Brain-Tool-
@@ -207,8 +207,8 @@ Zerfaellt in zwei Teile:
(Docker-Socket) + Controller mit Placement-Policy + Reconciliation + (Docker-Socket) + Controller mit Placement-Policy + Reconciliation +
Broadcast-Kollisions-Vermeidung (nicht 2× dieselbe Faehigkeit). = Mini-Nomad. Broadcast-Kollisions-Vermeidung (nicht 2× dieselbe Faehigkeit). = Mini-Nomad.
**Empfehlung:** Fuer 2 Gameboxen NICHT bauen — statische Platzierung reicht **Empfehlung:** Fuer 2 AI-Boxen NICHT bauen — statische Platzierung reicht
(Gamebox1=LLM, Gamebox2=Voice). Dynamisches Laden/Entladen zum VRAM-Freimachen (AI-Box1=LLM, AI-Box2=Voice). Dynamisches Laden/Entladen zum VRAM-Freimachen
deckt `llama-swap` innerhalb eines Hosts (B0.5). Waechst die Flotte: erst den deckt `llama-swap` innerhalb eines Hosts (B0.5). Waechst die Flotte: erst den
billigen Heartbeat-Teil; fuer echte Orchestrierung Docker Swarm / Nomad nehmen billigen Heartbeat-Teil; fuer echte Orchestrierung Docker Swarm / Nomad nehmen
statt selbst einen Scheduler zu bauen. statt selbst einen Scheduler zu bauen.
@@ -227,7 +227,7 @@ lohnt nicht):
einen Heartbeat via RVS (Host, GPU-Util, VRAM frei/belegt, laufende einen Heartbeat via RVS (Host, GPU-Util, VRAM frei/belegt, laufende
GPU-Container). Diagnostic zeigt pro Host VRAM-Balken + Dienste + „Host X hat GPU-Container). Diagnostic zeigt pro Host VRAM-Balken + Dienste + „Host X hat
N GB frei". Kein Start/Stop, nur Sicht + Hinweis wohin verschiebbar. N GB frei". Kein Start/Stop, nur Sicht + Hinweis wohin verschiebbar.
- **Zukunft (Gamebox3, 4×3060 = 48 GB):** neuer Host, eigenes Profil, `up` → - **Zukunft (AI-Box3, 4×3060 = 48 GB):** neuer Host, eigenes Profil, `up` →
erscheint im Dashboard; grosses lokales LLM oder FLUX-Vollausbau dorthin. erscheint im Dashboard; grosses lokales LLM oder FLUX-Vollausbau dorthin.
Ohne Orchestrator. Ohne Orchestrator.
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
ARIA FLUX-Bridge — laeuft auf der Gamebox (RTX 3060). ARIA FLUX-Bridge — laeuft auf der AI-Box (RTX 3060).
Empfaengt flux_request via RVS → FLUX.1-dev/-schnell auf GPU → sendet Empfaengt flux_request via RVS → FLUX.1-dev/-schnell auf GPU → sendet
flux_response mit base64-PNG zurueck an die aria-bridge. Diese speichert flux_response mit base64-PNG zurueck an die aria-bridge. Diese speichert
+5 -5
View File
@@ -183,9 +183,9 @@ Wichtige Mechanismen:
- [x] Decimal-zu-Worte fuer TTS (0.1 → null komma eins, mit IP-Schutz-Lookahead) - [x] Decimal-zu-Worte fuer TTS (0.1 → null komma eins, mit IP-Schutz-Lookahead)
- [x] Generic Acronym-Buchstabieren (XTTS → X T T S, USB → U S B, ueber expliziter Liste) - [x] Generic Acronym-Buchstabieren (XTTS → X T T S, USB → U S B, ueber expliziter Liste)
- [x] voice_preload/voice_ready: Stille Mini-Render bei Voice-Wechsel + Toast/Status "bereit" - [x] voice_preload/voice_ready: Stille Mini-Render bei Voice-Wechsel + Toast/Status "bereit"
- [x] Whisper STT auf die Gamebox ausgelagert (faster-whisper CUDA, float16) — neuer aria-whisper-bridge Container - [x] Whisper STT auf die AI-Box ausgelagert (faster-whisper CUDA, float16) — neuer aria-whisper-bridge Container
- [x] aria-bridge: STT primaer remote (Gamebox), Fallback lokal nach 45s Timeout - [x] aria-bridge: STT primaer remote (AI-Box), Fallback lokal nach 45s Timeout
- [x] Whisper-Modell hot-swap auf Gamebox via config-Broadcast aus Diagnostic - [x] Whisper-Modell hot-swap auf AI-Box via config-Broadcast aus Diagnostic
- [x] **F5-TTS ersetzt XTTS komplett** — neuer aria-f5tts-bridge Container, Voice Cloning, satzweises Streaming - [x] **F5-TTS ersetzt XTTS komplett** — neuer aria-f5tts-bridge Container, Voice Cloning, satzweises Streaming
- [x] Voice-Upload mit Whisper-Auto-Transkription — User muss keinen Referenz-Text eintippen - [x] Voice-Upload mit Whisper-Auto-Transkription — User muss keinen Referenz-Text eintippen
- [x] Audio-Pause statt Ducking: Spotify/YouTube pausieren komplett waehrend TTS (TRANSIENT statt MAY_DUCK) - [x] Audio-Pause statt Ducking: Spotify/YouTube pausieren komplett waehrend TTS (TRANSIENT statt MAY_DUCK)
@@ -334,7 +334,7 @@ Skills mit Tool-Use.
- [x] Datei-Manager (Diagnostic + App-Modal): /shared/uploads/ verwalten, Multi-Select + Select-All + Bulk-Download als ZIP + Bulk-Delete - [x] Datei-Manager (Diagnostic + App-Modal): /shared/uploads/ verwalten, Multi-Select + Select-All + Bulk-Download als ZIP + Bulk-Delete
- [x] Wipe-All-Button (Memory + Stimmen + Settings) - [x] Wipe-All-Button (Memory + Stimmen + Settings)
- [x] Voice Export/Import pro Stimme (Diagnostic + XTTS-Bridge auf Gamebox) - [x] Voice Export/Import pro Stimme (Diagnostic + XTTS-Bridge auf AI-Box)
- [x] F5/Whisper-Settings als JSON-Bundle Export/Import - [x] F5/Whisper-Settings als JSON-Bundle Export/Import
- [x] App Chat-Suche umgebaut: Highlight + Next/Prev statt Filter - [x] App Chat-Suche umgebaut: Highlight + Next/Prev statt Filter
- [x] App Pinch-Zoom in Bildern rewriten (Multi-Touch-Race-Bugs) - [x] App Pinch-Zoom in Bildern rewriten (Multi-Touch-Race-Bugs)
@@ -399,7 +399,7 @@ Skills mit Tool-Use.
### Architektur ### Architektur
- [ ] Diagnostic: System-Info Tab (Container-Status, Disk, RAM, CPU) - [ ] Diagnostic: System-Info Tab (Container-Status, Disk, RAM, CPU)
- [ ] RVS Zombie-Connections endgueltig loesen - [ ] RVS Zombie-Connections endgueltig loesen
- [ ] Gamebox: kleine Web-Oberflaeche fuer Credentials/Server-Config oder zentral aus Diagnostic per RVS push - [ ] AI-Box: kleine Web-Oberflaeche fuer Credentials/Server-Config oder zentral aus Diagnostic per RVS push
- [ ] Erste Skills bauen lassen (yt-dlp, pdf-extract, image-resize, etc.) — durch normale Anfragen, ARIA legt sie selbst an - [ ] Erste Skills bauen lassen (yt-dlp, pdf-extract, image-resize, etc.) — durch normale Anfragen, ARIA legt sie selbst an
- [ ] Heartbeat (periodische Selbst-Checks) - [ ] Heartbeat (periodische Selbst-Checks)
- [ ] Lokales LLM als Waechter (Triage vor Claude-Call) - [ ] Lokales LLM als Waechter (Triage vor Claude-Call)
+1 -1
View File
@@ -63,7 +63,7 @@ const ALLOWED_TYPES = new Set([
"agent_stream", "agent_stream",
"oauth_callback", "oauth_callback",
// Lokales LLM (Plan B) — Router im Brain schickt einfache Turns an das // Lokales LLM (Plan B) — Router im Brain schickt einfache Turns an das
// Qwen3 auf der Gamebox (via Bridge → RVS → llm-adapter → llama.cpp). // Qwen3 auf der AI-Box (via Bridge → RVS → llm-adapter → llama.cpp).
// llm_partial ist fuer B2 (Token-Streaming) reserviert, noch ungenutzt. // llm_partial ist fuer B2 (Token-Streaming) reserviert, noch ungenutzt.
"llm_request", "llm_response", "llm_partial", "llm_request", "llm_response", "llm_partial",
// Workspace-Desktop (Code-Projekte): Live-Code-Editor (CodeMirror in der App) // Workspace-Desktop (Code-Projekte): Live-Code-Editor (CodeMirror in der App)
+2 -2
View File
@@ -20,8 +20,8 @@ COMPOSE_PROFILES=whisper,f5tts
# ─── Node-Name ──────────────────────────────────── # ─── Node-Name ────────────────────────────────────
# Freier Name dieses Rechners. Erscheint in Diagnostic (Flotten-Anzeige) und # Freier Name dieses Rechners. Erscheint in Diagnostic (Flotten-Anzeige) und
# in den Logs, und bildet die Instanz-ID der Dienste (z.B. f5tts@gamebox). # in den Logs, und bildet die Instanz-ID der Dienste (z.B. f5tts@ai-box).
NODE_NAME=gamebox NODE_NAME=ai-box
# ─── GPU-Zuordnung pro Dienst ───────────────────── # ─── GPU-Zuordnung pro Dienst ─────────────────────
# Setzt NVIDIA_VISIBLE_DEVICES fuer den jeweiligen Container. # Setzt NVIDIA_VISIBLE_DEVICES fuer den jeweiligen Container.
+2 -2
View File
@@ -1,7 +1,7 @@
# ════════════════════════════════════════════════ # ════════════════════════════════════════════════
# ARIA Compute-Node — GPU-Dienste (STT / TTS / LLM) # ARIA Compute-Node — GPU-Dienste (STT / TTS / LLM)
# #
# KEINE feste "Gamebox" mehr: dieser Stack laeuft auf beliebig vielen # KEINE feste "AI-Box" mehr: dieser Stack laeuft auf beliebig vielen
# Worker-Nodes. Jeder Node startet ueber COMPOSE_PROFILES nur die Dienste, # Worker-Nodes. Jeder Node startet ueber COMPOSE_PROFILES nur die Dienste,
# die er anbieten soll, und pinnt sie per *_GPU auf bestimmte Grafikkarten. # die er anbieten soll, und pinnt sie per *_GPU auf bestimmte Grafikkarten.
# Alles verbindet sich ueber RVS mit der ARIA-Infrastruktur — kein VPN. # Alles verbindet sich ueber RVS mit der ARIA-Infrastruktur — kein VPN.
@@ -10,7 +10,7 @@
# STT-Box → COMPOSE_PROFILES=voxtral # STT-Box → COMPOSE_PROFILES=voxtral
# TTS-Box → COMPOSE_PROFILES=f5tts # TTS-Box → COMPOSE_PROFILES=f5tts
# LLM-Box → COMPOSE_PROFILES=llm # LLM-Box → COMPOSE_PROFILES=llm
# All-in-One → COMPOSE_PROFILES=voxtral,f5tts,llm (die alte Gamebox) # All-in-One → COMPOSE_PROFILES=voxtral,f5tts,llm (die alte AI-Box)
# #
# FLUX-Bildgenerierung liegt im /flux Verzeichnis — eigener Stack. # FLUX-Bildgenerierung liegt im /flux Verzeichnis — eigener Stack.
# ════════════════════════════════════════════════ # ════════════════════════════════════════════════
+2 -2
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
ARIA F5-TTS Bridge — laeuft auf der Gamebox (RTX 3060). ARIA F5-TTS Bridge — laeuft auf der AI-Box (RTX 3060).
Empfaengt xtts_request via RVS → F5-TTS Voice Cloning auf GPU → streamt Empfaengt xtts_request via RVS → F5-TTS Voice Cloning auf GPU → streamt
16-bit PCM Chunks als audio_pcm Nachrichten zurueck an die App. 16-bit PCM Chunks als audio_pcm Nachrichten zurueck an die App.
@@ -387,7 +387,7 @@ async def _send(ws, mtype: str, payload: dict) -> None:
# ────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────
# DEBUG-LOG ueber RVS → /shared/logs/app.log # DEBUG-LOG ueber RVS → /shared/logs/app.log
# #
# Gleiches Pattern wie in whisper-bridge: Stefan's Gamebox ist # Gleiches Pattern wie in whisper-bridge: Stefan's AI-Box ist
# Windows (kein SSH), in Zukunft koennten whisper + f5tts auf # Windows (kein SSH), in Zukunft koennten whisper + f5tts auf
# unterschiedlichen Hosts laufen. Logs ueber RVS heisst: ein Pfad. # unterschiedlichen Hosts laufen. Logs ueber RVS heisst: ein Pfad.
# #
+4 -4
View File
@@ -1,6 +1,6 @@
# Local-LLM-Adapter (Gamebox) — Plan B, Phase B0 # Local-LLM-Adapter (AI-Box) — Plan B, Phase B0
Bringt ein lokales, schnelles LLM (Qwen3 8B) auf die Gamebox und haengt es Bringt ein lokales, schnelles LLM (Qwen3 8B) auf die AI-Box und haengt es
per RVS an ARIA — fuer die einfachen ~80 % der Turns (<1 s), waehrend Claude per RVS an ARIA — fuer die einfachen ~80 % der Turns (<1 s), waehrend Claude
das Tiefen-Hirn bleibt. Siehe `docs/plan-local-llm-router.md` im Repo-Root. das Tiefen-Hirn bleibt. Siehe `docs/plan-local-llm-router.md` im Repo-Root.
@@ -17,7 +17,7 @@ das Tiefen-Hirn bleibt. Siehe `docs/plan-local-llm-router.md` im Repo-Root.
cached es unter `xtts/models/` (Bind-Mount → kein Re-Download bei Restart). cached es unter `xtts/models/` (Bind-Mount → kein Re-Download bei Restart).
Default: **Qwen3 8B, Q4_K_M** aus dem offiziellen Repo `Qwen/Qwen3-8B-GGUF`. Default: **Qwen3 8B, Q4_K_M** aus dem offiziellen Repo `Qwen/Qwen3-8B-GGUF`.
Modell/Quant wechseln = in der `.env` der Gamebox setzen (kein Code): Modell/Quant wechseln = in der `.env` der AI-Box setzen (kein Code):
``` ```
LLM_HF_REPO=Qwen/Qwen3-8B-GGUF # HF-Repo LLM_HF_REPO=Qwen/Qwen3-8B-GGUF # HF-Repo
@@ -35,7 +35,7 @@ umstellen + Container neu — Ein-Zeilen-Wechsel, kein Code.
Modelle) ist ein geplanter Folge-Baustein via `llama-swap` — siehe Modelle) ist ein geplanter Folge-Baustein via `llama-swap` — siehe
`docs/plan-local-llm-router.md`. `docs/plan-local-llm-router.md`.
## Start (auf der Gamebox) ## Start (auf der AI-Box)
```bash ```bash
cd xtts cd xtts
+2 -2
View File
@@ -1,5 +1,5 @@
""" """
ARIA Local-LLM-Adapter (Gamebox) — Plan B, Phase B0. ARIA Local-LLM-Adapter (AI-Box) — Plan B, Phase B0.
Bruecke zwischen RVS und dem lokalen llama.cpp-Server. Spiegelt das Muster der Bruecke zwischen RVS und dem lokalen llama.cpp-Server. Spiegelt das Muster der
whisper-bridge: verbindet sich per WebSocket mit dem RVS (Token-Room, TLS mit whisper-bridge: verbindet sich per WebSocket mit dem RVS (Token-Room, TLS mit
@@ -7,7 +7,7 @@ ws-Fallback, Reconnect-Backoff), lauscht auf `llm_request` und ruft den lokalen
llama.cpp-`/v1/chat/completions`-Endpoint (OpenAI-kompatibel), antwortet mit llama.cpp-`/v1/chat/completions`-Endpoint (OpenAI-kompatibel), antwortet mit
`llm_response` (korreliert per requestId). `llm_response` (korreliert per requestId).
Topologie: Gamebox steht zuhause, ARIA im RZ — die Kommunikation laeuft ueber Topologie: AI-Box steht zuhause, ARIA im RZ — die Kommunikation laeuft ueber
den RVS (wie TTS/STT), keine IPs zu pflegen. Nur URL + Token. den RVS (wie TTS/STT), keine IPs zu pflegen. Nur URL + Token.
Env: Env:
+3 -3
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
ARIA Whisper Bridge — laeuft auf der Gamebox (RTX 3060). ARIA Whisper Bridge — laeuft auf der AI-Box (RTX 3060).
Zwei Modi: Zwei Modi:
@@ -292,7 +292,7 @@ async def _send(ws, mtype: str, payload: dict) -> None:
# ────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────
# DEBUG-LOG ueber RVS → /shared/logs/app.log # DEBUG-LOG ueber RVS → /shared/logs/app.log
# #
# Stefan's Gamebox ist Windows, kein SSH → wir brauchen Whisper-Bridge- # Stefan's AI-Box ist Windows, kein SSH → wir brauchen Whisper-Bridge-
# Logs ueber den gleichen Pfad wie die App: app_log-Messages via RVS, # Logs ueber den gleichen Pfad wie die App: app_log-Messages via RVS,
# aria-bridge schreibt sie in /shared/logs/app.log. Diagnostic / App- # aria-bridge schreibt sie in /shared/logs/app.log. Diagnostic / App-
# Logs-Tab zeigen sie dann mit platform="whisper". # Logs-Tab zeigen sie dann mit platform="whisper".
@@ -881,7 +881,7 @@ async def run_loop(runner: WhisperRunner, sessions: SessionManager) -> None:
logger.info("Initial: sende config_request an aria-bridge") logger.info("Initial: sende config_request an aria-bridge")
await _send(ws, "config_request", {"service": "whisper"}) await _send(ws, "config_request", {"service": "whisper"})
# Startup-Marker — App-Logs zeigen damit ob Streaming-Code # Startup-Marker — App-Logs zeigen damit ob Streaming-Code
# ueberhaupt aktiv ist (Stefan baut auf Gamebox via PS, # ueberhaupt aktiv ist (Stefan baut auf AI-Box via PS,
# Build/Restart kann unbeabsichtigt alte Version weiterfahren). # Build/Restart kann unbeabsichtigt alte Version weiterfahren).
await _debug_log(ws, "boot", await _debug_log(ws, "boot",
"whisper-bridge online — streaming-mode ENABLED, debug-log ON") "whisper-bridge online — streaming-mode ENABLED, debug-log ON")