added claude cli log and test and optimize log windows through seperate tabs, update readme changelog
This commit is contained in:
parent
b3a2fd7092
commit
2e4a12c812
33
CHANGELOG.md
33
CHANGELOG.md
|
|
@ -4,22 +4,43 @@ Alle Änderungen am Projekt. Format: [Keep a Changelog](https://keepachangelog.c
|
|||
|
||||
---
|
||||
|
||||
## [0.0.0.4] — 2026-03-11
|
||||
## [0.0.0.4] — 2026-03-11 / 2026-03-12
|
||||
|
||||
### Hinzugefügt
|
||||
|
||||
**Diagnostic Container — Selbstcheck-UI**
|
||||
- Neuer Container `aria-diagnostic` mit Web-UI auf Port 3001
|
||||
- Status-Karten: OpenClaw Gateway, RVS, Claude Proxy — jeweils mit Dot-Indicator
|
||||
- Claude Proxy Test: Prüft Erreichbarkeit (`/v1/models`) und sendet Test-Prompt an Claude
|
||||
- Chat-Test: Nachrichten direkt über Gateway oder via RVS senden
|
||||
- Tabbed Logs: Separate Tabs für Alle, Gateway, RVS, Proxy, Server — mit Zähler pro Tab
|
||||
- Autoscroll-Pause: Automatisch wenn hochgescrollt, "Nach unten" Button zum Fortsetzen
|
||||
- TLS Fallback für RVS-Verbindung (wie Bridge und App)
|
||||
|
||||
### Geändert
|
||||
|
||||
**Bridge → aria-core: OpenClaw Gateway Protokoll**
|
||||
- Bridge nutzt jetzt das echte OpenClaw Gateway WebSocket-Protokoll (Port 18789 statt 8080)
|
||||
- Vollständiger Handshake: `connect.challenge` → `connect` Request (mit Auth-Token) → `hello-ok`
|
||||
- Nachrichten über `chat.send` Method mit `sessionKey` und `idempotencyKey`
|
||||
- Nachrichten über `chat.send` Method mit `message` und `idempotencyKey`
|
||||
- Antworten über `chat:final` Events (statt custom JSON)
|
||||
- Streaming-Support vorbereitet (`chat:delta` Events werden empfangen)
|
||||
- Fehlerbehandlung für `chat:error` Events — werden an die App weitergeleitet
|
||||
- Client-ID: `gateway-client` / Mode: `backend` (OpenClaw akzeptiert nur bestimmte Werte)
|
||||
|
||||
**Docker-Compose Fixes**
|
||||
- `OPENCLAW_GATEWAY_BIND=0.0.0.0` — Gateway bindet auf alle Interfaces (sonst nur 127.0.0.1, Bridge kann nicht zugreifen)
|
||||
- `OPENCLAW_GATEWAY_TOKEN` statt `AUTH_TOKEN` — korrekter Env-Var-Name fuer OpenClaw Gateway Auth
|
||||
- `ARIA_AUTH_TOKEN` an Bridge-Container durchgereicht — Bridge authentifiziert sich am Gateway
|
||||
**Docker-Compose Überarbeitung**
|
||||
- Bridge + Diagnostic nutzen `network_mode: "service:aria"` — teilen Netzwerk mit aria-core, kein separates Netz nötig
|
||||
- `ANTHROPIC_API_KEY` + `ANTHROPIC_BASE_URL` — OpenClaw nutzt Anthropic-Provider, nicht OpenAI
|
||||
- `DEFAULT_MODEL=claude-sonnet-4-6` — ohne `openai/` Prefix (Anthropic-Provider)
|
||||
- `OPENCLAW_GATEWAY_TOKEN` statt `AUTH_TOKEN` — korrekter Env-Var-Name
|
||||
- `ARIA_AUTH_TOKEN` an Bridge und Diagnostic durchgereicht
|
||||
- Port 3001 auf aria-Service gemappt (für Diagnostic Web-UI)
|
||||
|
||||
### Behoben
|
||||
|
||||
- Handshake fehlgeschlagen `[object Object]` — Fehlermeldung wurde nicht korrekt stringifiziert
|
||||
- `client.id` und `client.mode` im Connect-Request — OpenClaw akzeptiert nur vordefinierte Werte (`cli`, `gateway-client`, `webchat` etc.)
|
||||
- `chat.send` nutzt `message` statt `text` als Parameter — OpenClaw Schema-Validierung
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
61
README.md
61
README.md
|
|
@ -131,6 +131,10 @@ Ab da übernimmt ARIA. 😄
|
|||
│ │ ↕ WebSocket zu aria-core (lokal) │ │
|
||||
│ │ ↕ WebSocket zu RVS (öffentlich) │ │
|
||||
│ │ Brücke: App ⟷ RVS ⟷ Bridge ⟷ ARIA │ │
|
||||
│ │ │ │
|
||||
│ │ [diagnostic] Selbstcheck Web-UI (Port 3001) │ │
|
||||
│ │ Gateway + RVS + Proxy Status │ │
|
||||
│ │ Chat-Test, Tabbed Logs │ │
|
||||
│ └──────────────────┬──────────────────────────────┘ │
|
||||
│ │ Volume Mount │
|
||||
│ ▼ │
|
||||
|
|
@ -213,13 +217,16 @@ services:
|
|||
privileged: true # ARIAs Wohnung — sie hat die Schlüssel
|
||||
depends_on:
|
||||
- proxy
|
||||
ports:
|
||||
- "3001:3001" # Diagnostic Web-UI (laeuft im shared network)
|
||||
environment:
|
||||
- CANVAS_HOST=127.0.0.1
|
||||
- OPENCLAW_GATEWAY_BIND=0.0.0.0 # Bridge muss von Docker-Netz zugreifen
|
||||
- OPENCLAW_GATEWAY_TOKEN=${ARIA_AUTH_TOKEN}
|
||||
- OPENAI_API_KEY=not-needed
|
||||
- OPENAI_BASE_URL=http://proxy:3456/v1
|
||||
- DEFAULT_MODEL=openai/claude-sonnet-4-6
|
||||
- ANTHROPIC_API_KEY=not-needed
|
||||
- ANTHROPIC_BASE_URL=http://proxy:3456
|
||||
- DEFAULT_MODEL=claude-sonnet-4-6
|
||||
- RATE_LIMIT_PER_USER=30
|
||||
- DISPLAY=:0
|
||||
volumes:
|
||||
|
|
@ -240,6 +247,7 @@ services:
|
|||
container_name: aria-bridge
|
||||
depends_on:
|
||||
- aria
|
||||
network_mode: "service:aria" # Teilt Netzwerk mit aria-core → localhost:18789
|
||||
volumes:
|
||||
- ./aria-data/voices:/voices:ro # TTS Stimmen
|
||||
- ./aria-data/config/aria.env:/config/aria.env
|
||||
|
|
@ -257,8 +265,23 @@ services:
|
|||
- RVS_TLS_FALLBACK=${RVS_TLS_FALLBACK:-true}
|
||||
- RVS_TOKEN=${RVS_TOKEN:-}
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- aria-net
|
||||
|
||||
# ─── Diagnostic (Selbstcheck-UI) ──────────────────────
|
||||
diagnostic:
|
||||
build: ./diagnostic
|
||||
container_name: aria-diagnostic
|
||||
depends_on:
|
||||
- aria
|
||||
network_mode: "service:aria" # Teilt Netzwerk mit aria-core → localhost:18789
|
||||
environment:
|
||||
- ARIA_AUTH_TOKEN=${ARIA_AUTH_TOKEN:-}
|
||||
- PROXY_URL=http://proxy:3456
|
||||
- RVS_HOST=${RVS_HOST:-}
|
||||
- RVS_PORT=${RVS_PORT:-443}
|
||||
- RVS_TLS=${RVS_TLS:-true}
|
||||
- RVS_TLS_FALLBACK=${RVS_TLS_FALLBACK:-true}
|
||||
- RVS_TOKEN=${RVS_TOKEN:-}
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
aria-net:
|
||||
|
|
@ -697,6 +720,30 @@ cat aria-data/brain/memory/$(date +%Y-%m-%d).md
|
|||
|
||||
---
|
||||
|
||||
## Diagnostic — Selbstcheck-UI
|
||||
|
||||
Web-Dashboard zur Diagnose aller ARIA-Verbindungen. Läuft auf **Port 3001** der ARIA-VM.
|
||||
|
||||
```
|
||||
http://<ARIA-VM-IP>:3001
|
||||
```
|
||||
|
||||
**Status-Karten:**
|
||||
- **OpenClaw Gateway** — Verbindung + Handshake Status, Reconnect-Button
|
||||
- **RVS (Rendezvous)** — Verbindung mit TLS-Fallback, Reconnect-Button
|
||||
- **Claude Proxy** — Erreichbarkeit + Test-Prompt an Claude (prüft ob Credentials gültig sind)
|
||||
|
||||
**Chat-Test:**
|
||||
- "Gateway senden" — Nachricht direkt an OpenClaw, Antwort wird angezeigt
|
||||
- "Via RVS senden" — Nachricht über den Rendezvous-Server
|
||||
|
||||
**Tabbed Logs:**
|
||||
Separate Tabs mit Zähler: Alle | Gateway | RVS | Proxy | Server. Autoscroll pausiert automatisch beim Hochscrollen.
|
||||
|
||||
> Der Diagnostic-Container teilt das Netzwerk mit aria-core (`network_mode: "service:aria"`), deshalb erreicht er den Gateway auf `localhost:18789` und den Proxy über das Docker-Netz.
|
||||
|
||||
---
|
||||
|
||||
## Repo-Struktur
|
||||
|
||||
```
|
||||
|
|
@ -728,6 +775,12 @@ aria/ ← Gitea Repo — hier wird entwickelt
|
|||
│ ├── aria_bridge.py ← Wake-Word + Whisper STT + Piper TTS
|
||||
│ └── modes.py ← Betriebsmodi
|
||||
│
|
||||
├── diagnostic/ ← Selbstcheck Web-UI (Port 3001)
|
||||
│ ├── Dockerfile
|
||||
│ ├── server.js ← Gateway + RVS + Proxy Tests
|
||||
│ ├── index.html ← Dashboard mit Tabs
|
||||
│ └── package.json
|
||||
│
|
||||
├── rvs/ ← Rendezvous-Server (Rechenzentrum)
|
||||
│ ├── docker-compose.yml ← eigenes Compose — separat deployen!
|
||||
│ ├── server.js ← WebSocket Relay — reiner Durchleiter
|
||||
|
|
|
|||
|
|
@ -10,20 +10,22 @@
|
|||
h1 { font-size: 20px; margin-bottom: 16px; color: #0096FF; }
|
||||
h2 { font-size: 14px; margin-bottom: 8px; color: #8888AA; text-transform: uppercase; letter-spacing: 1px; }
|
||||
|
||||
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }
|
||||
.grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; margin-bottom: 16px; }
|
||||
.card { background: #12122A; border: 1px solid #1E1E2E; border-radius: 8px; padding: 12px; }
|
||||
.card.full { grid-column: 1 / -1; }
|
||||
|
||||
.status-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
||||
.dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
||||
.dot.connected { background: #34C759; box-shadow: 0 0 6px #34C759; }
|
||||
.dot.connecting { background: #FFD60A; }
|
||||
.dot.connecting, .dot.testing { background: #FFD60A; animation: pulse 1s infinite; }
|
||||
.dot.disconnected { background: #FF3B30; }
|
||||
.dot.error { background: #FF3B30; box-shadow: 0 0 6px #FF3B30; }
|
||||
.dot.not_configured { background: #555570; }
|
||||
.dot.unknown { background: #555570; }
|
||||
@keyframes pulse { 50% { opacity: 0.5; } }
|
||||
|
||||
.status-label { font-size: 13px; }
|
||||
.error-text { color: #FF6B6B; font-size: 11px; margin-top: 4px; }
|
||||
.error-text { color: #FF6B6B; font-size: 11px; margin-top: 4px; word-break: break-all; }
|
||||
|
||||
.btn { background: #0096FF; color: #fff; border: none; border-radius: 6px; padding: 8px 16px;
|
||||
font-family: inherit; font-size: 13px; cursor: pointer; margin: 4px 4px 4px 0; }
|
||||
|
|
@ -32,12 +34,23 @@
|
|||
.btn.secondary { background: #1E1E2E; border: 1px solid #333; }
|
||||
.btn.secondary:hover { background: #2A2A3E; }
|
||||
|
||||
.log-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
|
||||
.log-header h2 { margin-bottom: 0; }
|
||||
/* Tabs */
|
||||
.tab-bar { display: flex; gap: 2px; margin-bottom: 0; }
|
||||
.tab-btn { background: #1E1E2E; color: #8888AA; border: 1px solid #1E1E2E; border-bottom: none;
|
||||
border-radius: 6px 6px 0 0; padding: 6px 14px; font-family: inherit; font-size: 12px;
|
||||
cursor: pointer; position: relative; top: 1px; }
|
||||
.tab-btn.active { background: #080810; color: #E0E0F0; border-color: #1E1E2E; }
|
||||
.tab-btn .tab-count { background: #333; color: #888; border-radius: 8px; padding: 1px 6px;
|
||||
font-size: 10px; margin-left: 4px; }
|
||||
|
||||
.log-header { display: flex; align-items: center; justify-content: space-between; padding: 6px 8px 4px; }
|
||||
.log-header h2 { margin-bottom: 0; font-size: 12px; }
|
||||
.pause-hint { font-size: 11px; color: #FFD60A; display: none; }
|
||||
.pause-hint.visible { display: inline; }
|
||||
.log-box { background: #080810; border: 1px solid #1E1E2E; border-radius: 6px;
|
||||
height: 300px; overflow-y: auto; padding: 8px; font-size: 11px; line-height: 1.6; position: relative; }
|
||||
|
||||
.log-panel { background: #080810; border: 1px solid #1E1E2E; border-radius: 0 0 6px 6px; }
|
||||
.log-box { height: 280px; overflow-y: auto; padding: 8px; font-size: 11px; line-height: 1.6; }
|
||||
.log-box.hidden { display: none; }
|
||||
.log-entry { white-space: pre-wrap; word-break: break-all; }
|
||||
.log-entry.error { color: #FF6B6B; }
|
||||
.log-entry.warn { color: #FFD60A; }
|
||||
|
|
@ -81,6 +94,16 @@
|
|||
<div class="error-text" id="rvs-error"></div>
|
||||
<button class="btn secondary" onclick="send({action:'reconnect_rvs'})">Reconnect</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Claude Proxy</h2>
|
||||
<div class="status-row">
|
||||
<div class="dot" id="proxy-dot"></div>
|
||||
<span class="status-label" id="proxy-status">Nicht getestet</span>
|
||||
</div>
|
||||
<div class="error-text" id="proxy-error"></div>
|
||||
<button class="btn secondary" id="btn-proxy-test" onclick="testProxyBtn()">Proxy testen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Test -->
|
||||
|
|
@ -96,47 +119,108 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Verbindungslog -->
|
||||
<div class="card" style="margin-top:12px">
|
||||
<div class="log-header">
|
||||
<h2>Verbindungslog</h2>
|
||||
<span>
|
||||
<span class="pause-hint" id="pause-hint">Autoscroll pausiert</span>
|
||||
<button class="btn secondary" id="btn-scroll" onclick="resumeScroll()" style="display:none;padding:4px 10px;font-size:11px">Nach unten</button>
|
||||
</span>
|
||||
<!-- Logs mit Tabs -->
|
||||
<div class="card" style="margin-top:12px; padding: 8px 0 0 0;">
|
||||
<div style="padding: 0 12px;">
|
||||
<div class="tab-bar">
|
||||
<button class="tab-btn active" data-tab="all" onclick="switchTab('all')">Alle <span class="tab-count" id="count-all">0</span></button>
|
||||
<button class="tab-btn" data-tab="gateway" onclick="switchTab('gateway')">Gateway <span class="tab-count" id="count-gateway">0</span></button>
|
||||
<button class="tab-btn" data-tab="rvs" onclick="switchTab('rvs')">RVS <span class="tab-count" id="count-rvs">0</span></button>
|
||||
<button class="tab-btn" data-tab="proxy" onclick="switchTab('proxy')">Proxy <span class="tab-count" id="count-proxy">0</span></button>
|
||||
<button class="tab-btn" data-tab="server" onclick="switchTab('server')">Server <span class="tab-count" id="count-server">0</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-panel">
|
||||
<div class="log-header">
|
||||
<span>
|
||||
<span class="pause-hint" id="pause-hint">Autoscroll pausiert</span>
|
||||
<button class="btn secondary" id="btn-scroll" onclick="resumeScroll()" style="display:none;padding:4px 10px;font-size:11px">Nach unten</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="log-box" id="log-all"></div>
|
||||
<div class="log-box hidden" id="log-gateway"></div>
|
||||
<div class="log-box hidden" id="log-rvs"></div>
|
||||
<div class="log-box hidden" id="log-proxy"></div>
|
||||
<div class="log-box hidden" id="log-server"></div>
|
||||
</div>
|
||||
<div class="log-box" id="log-box"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const logBox = document.getElementById('log-box');
|
||||
const chatBox = document.getElementById('chat-box');
|
||||
const pauseHint = document.getElementById('pause-hint');
|
||||
const btnScroll = document.getElementById('btn-scroll');
|
||||
let ws;
|
||||
let logAutoScroll = true;
|
||||
let activeTab = 'all';
|
||||
const autoScroll = { all: true, gateway: true, rvs: true, proxy: true, server: true };
|
||||
const logCounts = { all: 0, gateway: 0, rvs: 0, proxy: 0, server: 0 };
|
||||
|
||||
// Autoscroll pausiert wenn User hochscrollt
|
||||
logBox.addEventListener('scroll', () => {
|
||||
const atBottom = logBox.scrollHeight - logBox.scrollTop - logBox.clientHeight < 30;
|
||||
logAutoScroll = atBottom;
|
||||
pauseHint.classList.toggle('visible', !atBottom);
|
||||
btnScroll.style.display = atBottom ? 'none' : 'inline';
|
||||
const logBoxes = {
|
||||
all: document.getElementById('log-all'),
|
||||
gateway: document.getElementById('log-gateway'),
|
||||
rvs: document.getElementById('log-rvs'),
|
||||
proxy: document.getElementById('log-proxy'),
|
||||
server: document.getElementById('log-server'),
|
||||
};
|
||||
|
||||
// Scroll-Pause pro aktivem Tab
|
||||
Object.entries(logBoxes).forEach(([tab, box]) => {
|
||||
box.addEventListener('scroll', () => {
|
||||
const atBottom = box.scrollHeight - box.scrollTop - box.clientHeight < 30;
|
||||
autoScroll[tab] = atBottom;
|
||||
if (tab === activeTab) {
|
||||
pauseHint.classList.toggle('visible', !atBottom);
|
||||
btnScroll.style.display = atBottom ? 'none' : 'inline';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function resumeScroll() {
|
||||
logAutoScroll = true;
|
||||
logBox.scrollTop = logBox.scrollHeight;
|
||||
autoScroll[activeTab] = true;
|
||||
const box = logBoxes[activeTab];
|
||||
box.scrollTop = box.scrollHeight;
|
||||
pauseHint.classList.remove('visible');
|
||||
btnScroll.style.display = 'none';
|
||||
}
|
||||
|
||||
function switchTab(tab) {
|
||||
activeTab = tab;
|
||||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.toggle('active', b.dataset.tab === tab));
|
||||
Object.entries(logBoxes).forEach(([t, box]) => box.classList.toggle('hidden', t !== tab));
|
||||
// Pause-Hinweis aktualisieren
|
||||
const box = logBoxes[tab];
|
||||
const atBottom = box.scrollHeight - box.scrollTop - box.clientHeight < 30;
|
||||
pauseHint.classList.toggle('visible', !atBottom);
|
||||
btnScroll.style.display = atBottom ? 'none' : 'inline';
|
||||
if (autoScroll[tab]) box.scrollTop = box.scrollHeight;
|
||||
}
|
||||
|
||||
function updateCount(source) {
|
||||
logCounts.all++;
|
||||
document.getElementById('count-all').textContent = logCounts.all;
|
||||
// source → tab mapping
|
||||
const tab = mapSourceToTab(source);
|
||||
if (tab && logCounts[tab] !== undefined) {
|
||||
logCounts[tab]++;
|
||||
document.getElementById(`count-${tab}`).textContent = logCounts[tab];
|
||||
}
|
||||
}
|
||||
|
||||
function mapSourceToTab(source) {
|
||||
if (source === 'gateway') return 'gateway';
|
||||
if (source === 'rvs') return 'rvs';
|
||||
if (source === 'proxy') return 'proxy';
|
||||
if (source === 'server' || source === 'browser') return 'server';
|
||||
return null;
|
||||
}
|
||||
|
||||
const STATUS_LABELS = {
|
||||
connected: 'Verbunden',
|
||||
connecting: 'Verbinde...',
|
||||
testing: 'Teste...',
|
||||
disconnected: 'Getrennt',
|
||||
error: 'Fehler',
|
||||
not_configured: 'Nicht konfiguriert',
|
||||
unknown: 'Nicht getestet',
|
||||
};
|
||||
|
||||
function connectWS() {
|
||||
|
|
@ -165,10 +249,7 @@
|
|||
addChat('received', msg.text, 'chat:final');
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'chat_delta') {
|
||||
// Deltas optional anzeigen
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'chat_delta') { return; }
|
||||
if (msg.type === 'chat_error') {
|
||||
addChat('error', msg.error, 'chat:error');
|
||||
return;
|
||||
|
|
@ -178,10 +259,15 @@
|
|||
addChat('received', p.text || '?', `via RVS (${p.sender || '?'})`);
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'response') {
|
||||
// chat.send Ack — nur loggen
|
||||
if (msg.type === 'proxy_result') {
|
||||
if (msg.ok) {
|
||||
addChat('received', msg.reply, 'Claude Proxy direkt');
|
||||
} else {
|
||||
addChat('error', msg.error, 'Claude Proxy Fehler');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'response') { return; }
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -205,6 +291,10 @@
|
|||
send({ action: 'test_rvs', text });
|
||||
}
|
||||
|
||||
function testProxyBtn() {
|
||||
send({ action: 'test_proxy', text: 'Antworte mit genau einem Wort: Ping' });
|
||||
}
|
||||
|
||||
function updateState(state) {
|
||||
// Gateway
|
||||
const gw = state.gateway || {};
|
||||
|
|
@ -219,6 +309,13 @@
|
|||
document.getElementById('rvs-status').textContent = STATUS_LABELS[rvs.status] || rvs.status;
|
||||
document.getElementById('rvs-error').textContent = rvs.lastError || '';
|
||||
|
||||
// Proxy
|
||||
const proxy = state.proxy || {};
|
||||
document.getElementById('proxy-dot').className = `dot ${proxy.status || 'unknown'}`;
|
||||
document.getElementById('proxy-status').textContent = STATUS_LABELS[proxy.status] || proxy.status;
|
||||
document.getElementById('proxy-error').textContent = proxy.lastError || '';
|
||||
document.getElementById('btn-proxy-test').disabled = proxy.status === 'testing';
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btn-gw').disabled = gw.status !== 'connected';
|
||||
document.getElementById('btn-rvs').disabled = rvs.status !== 'connected';
|
||||
|
|
@ -226,11 +323,23 @@
|
|||
|
||||
function addLog(level, source, message, ts) {
|
||||
const time = ts ? new Date(ts).toLocaleTimeString('de-DE') : new Date().toLocaleTimeString('de-DE');
|
||||
const line = `${time} [${source}] ${message}`;
|
||||
|
||||
// In "Alle" und in den passenden Tab schreiben
|
||||
appendToLog('all', level, line);
|
||||
const tab = mapSourceToTab(source);
|
||||
if (tab) appendToLog(tab, level, line);
|
||||
|
||||
updateCount(source);
|
||||
}
|
||||
|
||||
function appendToLog(tab, level, line) {
|
||||
const box = logBoxes[tab];
|
||||
const el = document.createElement('div');
|
||||
el.className = `log-entry ${level}`;
|
||||
el.textContent = `${time} [${source}] ${message}`;
|
||||
logBox.appendChild(el);
|
||||
if (logAutoScroll) logBox.scrollTop = logBox.scrollHeight;
|
||||
el.textContent = line;
|
||||
box.appendChild(el);
|
||||
if (autoScroll[tab]) box.scrollTop = box.scrollHeight;
|
||||
}
|
||||
|
||||
function addChat(type, text, meta) {
|
||||
|
|
|
|||
|
|
@ -27,11 +27,13 @@ const RVS_PORT = process.env.RVS_PORT || "443";
|
|||
const RVS_TLS = process.env.RVS_TLS || "true";
|
||||
const RVS_TLS_FALLBACK = process.env.RVS_TLS_FALLBACK || "true";
|
||||
const RVS_TOKEN = process.env.RVS_TOKEN || "";
|
||||
const PROXY_URL = process.env.PROXY_URL || "http://proxy:3456";
|
||||
|
||||
// ── State ───────────────────────────────────────────────
|
||||
const state = {
|
||||
gateway: { status: "disconnected", lastError: null, handshakeOk: false },
|
||||
rvs: { status: "disconnected", lastError: null },
|
||||
proxy: { status: "unknown", lastError: null },
|
||||
};
|
||||
const logs = [];
|
||||
let gatewayWs = null;
|
||||
|
|
@ -340,6 +342,73 @@ function sendToRVS(text) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// ── Claude Proxy Test ────────────────────────────────────
|
||||
|
||||
async function testProxy(prompt) {
|
||||
state.proxy.status = "testing";
|
||||
state.proxy.lastError = null;
|
||||
broadcastState();
|
||||
log("info", "proxy", `Teste Proxy: ${PROXY_URL}`);
|
||||
|
||||
try {
|
||||
// Schritt 1: Erreichbarkeit pruefen
|
||||
const healthUrl = `${PROXY_URL}/v1/models`;
|
||||
log("info", "proxy", `Rufe ab: ${healthUrl}`);
|
||||
|
||||
const modelsRes = await fetch(healthUrl, {
|
||||
headers: { "Authorization": "Bearer not-needed" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (!modelsRes.ok) {
|
||||
throw new Error(`Models-Endpoint: HTTP ${modelsRes.status} ${modelsRes.statusText}`);
|
||||
}
|
||||
|
||||
const modelsData = await modelsRes.json();
|
||||
const modelCount = modelsData.data?.length || 0;
|
||||
log("info", "proxy", `Proxy erreichbar — ${modelCount} Model(s) verfuegbar`);
|
||||
|
||||
// Schritt 2: Chat Completion testen (kurzer Prompt)
|
||||
const testPrompt = prompt || "Antworte mit genau einem Wort: Ping";
|
||||
log("info", "proxy", `Sende Test-Prompt: "${testPrompt}"`);
|
||||
|
||||
const chatRes = await fetch(`${PROXY_URL}/v1/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer not-needed",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "claude-sonnet-4-6",
|
||||
messages: [{ role: "user", content: testPrompt }],
|
||||
max_tokens: 200,
|
||||
}),
|
||||
signal: AbortSignal.timeout(30000),
|
||||
});
|
||||
|
||||
if (!chatRes.ok) {
|
||||
const errBody = await chatRes.text().catch(() => "");
|
||||
throw new Error(`Chat-Completion: HTTP ${chatRes.status} — ${errBody.slice(0, 300)}`);
|
||||
}
|
||||
|
||||
const chatData = await chatRes.json();
|
||||
const reply = chatData.choices?.[0]?.message?.content || "(leer)";
|
||||
log("info", "proxy", `Antwort: "${reply.slice(0, 200)}"`);
|
||||
|
||||
state.proxy.status = "connected";
|
||||
state.proxy.lastError = null;
|
||||
broadcastState();
|
||||
broadcast({ type: "proxy_result", ok: true, reply });
|
||||
|
||||
} catch (err) {
|
||||
log("error", "proxy", `Fehler: ${err.message}`);
|
||||
state.proxy.status = "error";
|
||||
state.proxy.lastError = err.message;
|
||||
broadcastState();
|
||||
broadcast({ type: "proxy_result", ok: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
// ── Hilfsfunktionen ─────────────────────────────────────
|
||||
|
||||
function waitForMessage(ws, timeoutMs) {
|
||||
|
|
@ -391,6 +460,8 @@ wss.on("connection", (ws) => {
|
|||
connectGateway();
|
||||
} else if (msg.action === "reconnect_rvs") {
|
||||
connectRVS();
|
||||
} else if (msg.action === "test_proxy") {
|
||||
testProxy(msg.text);
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
|
|
@ -407,6 +478,7 @@ server.listen(HTTP_PORT, "0.0.0.0", () => {
|
|||
log("info", "server", `Gateway: ${GATEWAY_URL}`);
|
||||
log("info", "server", `Token: ${GATEWAY_TOKEN ? GATEWAY_TOKEN.slice(0, 8) + "..." : "(keiner)"}`);
|
||||
log("info", "server", `RVS: ${RVS_HOST ? `${RVS_HOST}:${RVS_PORT}` : "(nicht konfiguriert)"}`);
|
||||
log("info", "server", `Proxy: ${PROXY_URL}`);
|
||||
|
||||
// Verbindungen aufbauen
|
||||
connectGateway();
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ services:
|
|||
network_mode: "service:aria" # Teilt Netzwerk mit aria-core → localhost:18789
|
||||
environment:
|
||||
- ARIA_AUTH_TOKEN=${ARIA_AUTH_TOKEN:-}
|
||||
- PROXY_URL=http://proxy:3456
|
||||
- RVS_HOST=${RVS_HOST:-}
|
||||
- RVS_PORT=${RVS_PORT:-443}
|
||||
- RVS_TLS=${RVS_TLS:-true}
|
||||
|
|
|
|||
Loading…
Reference in New Issue