Feat: Verbinden-Tab Rework — zwei Leitungen (Modell + Gedaechtnis) sichtbar getrennt

- Hero-Diagramm: lokaler Agent -> Leitung 1 (Gateway/Modell) + Leitung 2 (MCP/Gedaechtnis)
- Live-Status pro Leitung: neuer Endpoint GET /api/connect/health (llama-swap + mem0-Sidecar)
- Zwei nummerierte Setup-Spalten statt flacher Tab-Leiste; Memory-MCP aus tools{} geloest
- Claude-Code-Snippet: echte env-Anleitung (ANTHROPIC_BASE_URL/AUTH_TOKEN/MODEL + Shim-Hinweis),
  da Gateway kein /v1/messages bietet (live verifiziert: :9001 405, :8080 404)
- MCP-Scriptpfad jetzt klar nur Leitung 2 zugeordnet

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-27 22:30:32 +02:00
parent d3157d2535
commit f82729f88e
10 changed files with 428 additions and 207 deletions
+7 -1
View File
@@ -2,7 +2,7 @@
from fastapi import APIRouter
from services.connect import DEFAULT_HOST, build_snippets
from services.connect import DEFAULT_HOST, build_snippets, check_health
router = APIRouter(prefix="/api")
@@ -13,3 +13,9 @@ def connect(host: str = DEFAULT_HOST, mcp_path: str | None = None) -> dict:
if mcp_path:
kwargs["mcp_script_path"] = mcp_path
return build_snippets(host=host, **kwargs)
@router.get("/connect/health")
def connect_health() -> dict:
"""Live-Status der zwei Leitungen (Gateway + Gedächtnis) für den Verbinden-Tab."""
return check_health()
+51 -11
View File
@@ -9,7 +9,9 @@ die häufigste Fehlerquelle. Der Aufrufer übergibt den Host explizit.
import json
from config import PORT
import httpx
from config import LLAMA_SWAP_URL, MEM0_SERVICE_URL, PORT
DEFAULT_HOST = "192.168.178.151"
@@ -79,12 +81,20 @@ def build_snippets(host: str = DEFAULT_HOST,
]
}, indent=2)
# Claude Code spricht Anthropic-Format; der eingebaute Gateway ist OpenAI-kompatibel.
# Claude Code spricht das Anthropic-Format; der Gateway ist OpenAI-kompatibel und
# bietet KEIN /v1/messages (verifiziert). Daher braucht es einen kleinen Übersetzer
# (Anthropic ⇄ OpenAI) als Aufsatz. Die env-Vars sind Claude Codes echte Schnittstelle.
claude_code = (
f"# Der eingebaute Gateway ist OpenAI-kompatibel ({gw}, model: auto).\n"
f"# Claude Code nutzt das Anthropic-Format — dafür braucht es einen Anthropic-Shim\n"
f"# (z.B. LiteLLM /v1/messages) als Aufsatz. Für lokale Modelle direkt: Cline / OpenCode /\n"
f"# Continue / Zed nutzen (oben), die sprechen OpenAI-kompatibel mit diesem Gateway."
f"# Claude Code spricht das Anthropic-Format — der Gateway ist OpenAI-kompatibel ({gw})\n"
f"# und hat kein /v1/messages. Dazwischen muss ein Übersetzer (Anthropic ⇄ OpenAI) laufen:\n"
f"# • claude-code-router (leichtgewichtig, npm)\n"
f"# • oder LiteLLM mit /v1/messages-Bridge\n"
f"# Den Übersetzer auf den Gateway zeigen lassen: baseURL={gw}, model=auto, apiKey=local.\n"
f"# Dann Claude Code auf den lokalen Übersetzer richten (Beispiel-Port 3456):\n"
f"\n"
f'export ANTHROPIC_BASE_URL="http://localhost:3456"\n'
f'export ANTHROPIC_AUTH_TOKEN="local"\n'
f'export ANTHROPIC_MODEL="auto"'
)
memory_mcp = json.dumps({
@@ -100,10 +110,12 @@ def build_snippets(host: str = DEFAULT_HOST,
return {
"host": host,
"gateway_url": gw,
"mc_url": mc_url,
# Leitung 1 — das MODELL. Alle Snippets zeigen auf den OpenAI-kompatiblen Gateway.
"tools": {
"cline": {"label": "Roo Code / Cline (VS Code)", "lang": "json", "snippet": cline,
"cline": {"label": "Roo Code / Cline", "lang": "json", "snippet": cline,
"note": "OpenAI-Provider → Gateway. Modell 'auto' (schnell, eskaliert bei Bedarf)."},
"cursor": {"label": "Cursor IDE", "lang": "json", "snippet": cursor,
"cursor": {"label": "Cursor", "lang": "json", "snippet": cursor,
"note": "Einstellungen ➔ Models ➔ OpenAI API key + Base URL."},
"opencode": {"label": "OpenCode", "lang": "jsonc", "snippet": opencode,
"note": "Datei opencode.jsonc, Key 'provider'."},
@@ -112,8 +124,36 @@ def build_snippets(host: str = DEFAULT_HOST,
"continue": {"label": "Continue", "lang": "json", "snippet": cont,
"note": "~/.continue/config.json (oder config.yaml mit identischen Keys)."},
"claude_code": {"label": "Claude Code", "lang": "bash", "snippet": claude_code,
"note": "Anthropic-Format über LiteLLM /v1/messages."},
"memory_mcp": {"label": "Shared Memory (MCP)", "lang": "json", "snippet": memory_mcp,
"note": "Für jedes MCP-fähige Tool. mcp_memory.py muss lokal liegen."},
"note": "Braucht einen Anthropic⇄OpenAI-Übersetzer vor dem Gateway."},
},
# Leitung 2 — das GEDÄCHTNIS. Separater MCP-Server, gilt zusätzlich zu jedem Tool oben.
"memory": {"label": "Shared Memory (MCP)", "lang": "json", "snippet": memory_mcp,
"note": "Eigene Leitung: MCP-Block für jedes MCP-fähige Tool. mcp_memory.py muss lokal liegen."},
}
def check_health() -> dict:
"""Live-Erreichbarkeit der beiden Leitungen, aus Sicht der Box:
Leitung 1 = Gateway/Engine (llama-swap), Leitung 2 = Gedächtnis-Sidecar (Mem0)."""
gateway = {"ok": False, "detail": "nicht erreichbar"}
try:
with httpx.Client(timeout=3.0) as c:
r = c.get(f"{LLAMA_SWAP_URL}/v1/models")
if r.status_code == 200:
n = len(r.json().get("data", []))
gateway = {"ok": True, "detail": f"{n} Modelle verfügbar" if n else "bereit"}
else:
gateway = {"ok": False, "detail": f"HTTP {r.status_code}"}
except Exception: # noqa: BLE001
pass
memory = {"ok": False, "detail": "nicht erreichbar"}
try:
with httpx.Client(timeout=3.0) as c:
r = c.get(f"{MEM0_SERVICE_URL}/health")
memory = ({"ok": True, "detail": "bereit"} if r.status_code == 200
else {"ok": False, "detail": f"HTTP {r.status_code}"})
except Exception: # noqa: BLE001
pass
return {"gateway": gateway, "memory": memory}