This repository has been archived on 2026-07-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
mission-control/static/js/panels/connect.js
T
Hitonabi 0ed4dd84b8 feat(verbinden): verifizierter Zed-Guide + proxy-feste Engine-Adresse
- Zed als empfohlene IDE: fertige settings.json (language_models.
  openai_compatible.bosgame) mit available_models aus dem echten Stack,
  Vision-Modelle bekommen capabilities.images; API-Key-Hinweis (UI/Env,
  nicht in settings.json) — Struktur gegen Zed-Doku verifiziert.
- baseUrl() nutzt jetzt fix http://<host>:8080/v1 (statt location.protocol),
  damit der Aufruf hinter einem Reverse-Proxy (NPM/HTTPS) nicht in eine
  kaputte https-:8080-URL kippt.
- Erkennt Proxy-Zugriff (behindProxy) und warnt: Engine-API laeuft direkt
  am Bosgame auf :8080 ohne HTTPS -> rohe LAN-IP eintragen, nicht den Namen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 16:39:51 +02:00

234 lines
12 KiB
JavaScript

// connect.js — „Verbinden" (v4): IDEs/Agenten anbinden, Verbindung testen, Bild/Vision-Flow.
import { api } from "../core/api.js";
import { $, esc, icon } from "../core/ui.js";
let S = null;
let lastSig = "";
window.mcCopyVal = btn => {
const i = btn.previousElementSibling;
navigator.clipboard?.writeText(i.value);
const o = btn.textContent; btn.textContent = "Kopiert!"; setTimeout(() => (btn.textContent = o), 1200);
};
window.mcCopyCode = btn => {
const code = btn.parentElement.querySelector("code");
navigator.clipboard?.writeText(code.textContent);
const o = btn.textContent; btn.textContent = "Kopiert!"; setTimeout(() => (btn.textContent = o), 1200);
};
window.cnGo = v => document.querySelector(`.nav-item[data-view="${v}"]`)?.click();
// Die Engine-API spricht IMMER reines HTTP auf Port 8080 direkt am Bosgame.
// Bewusst nicht location.protocol uebernehmen: wer MC hinter einem Reverse-Proxy
// (NPM) ueber https://<name> aufruft, bekaeme sonst eine kaputte https-:8080-URL.
function baseUrl() { return `http://${location.hostname}:8080/v1`; }
function isLanIp(h) { return /^\d{1,3}(\.\d{1,3}){3}$/.test(h); }
// true, wenn MC offenbar ueber einen Proxy/Namen aufgerufen wird (nicht direkt per IP).
function behindProxy() { return location.protocol === "https:" || !isLanIp(location.hostname); }
function models() { return S?.models || []; }
// Fertige opencode.json fuer einen OpenAI-kompatiblen Provider auf llama-swap.
function openCodeConfig(url, ids) {
const list = ids.length ? ids : ["coder"];
const modelMap = list.map(id => ` "${id}": { "name": "${id}" }`).join(",\n");
return `{
"$schema": "https://opencode.ai/config.json",
"provider": {
"llama-swap": {
"npm": "@ai-sdk/openai-compatible",
"name": "Bosgame (llama-swap)",
"options": {
"baseURL": "${url}",
"apiKey": "local"
},
"models": {
${modelMap}
}
}
}
}`;
}
// Fertige Zed-settings.json fuer einen OpenAI-kompatiblen Provider auf llama-swap.
// Vision-Modelle bekommen capabilities.images=true, sonst sieht Zed sie als reine Text-Modelle.
function zedConfig(url) {
const ms = models();
const list = ms.length ? ms : [{ name: "coder" }];
const entries = list.map(m => {
const img = (m.meta?.caps || []).some(c => /Bild|Vision/i.test(c));
const caps = img ? `, "capabilities": { "tools": true, "images": true }` : "";
return ` { "name": "${m.name}", "display_name": "${m.name}", "max_tokens": 32768${caps} }`;
}).join(",\n");
return `{
"language_models": {
"openai_compatible": {
"bosgame": {
"api_url": "${url}",
"available_models": [
${entries}
]
}
}
}
}`;
}
function field(label, value) {
return `<label>${label}</label>
<div class="flex gap-2" style="align-items:stretch;margin-bottom:12px">
<input class="mono-sm" readonly value="${esc(value)}" style="flex:1;margin:0">
<button class="ghost" onclick="window.mcCopyVal(this)">Kopieren</button>
</div>`;
}
const okChip = txt => `<span class="chip" style="background:rgba(63,185,80,.12);border-color:rgba(63,185,80,.25);color:#7ee29a">${icon("check")}${esc(txt)}</span>`;
function toolRow(name, desc, url, badge) {
return `<div class="li" style="align-items:flex-start">
<div class="li-main">
<div class="flex items-center gap-2"><span class="li-id" style="font-family:var(--sans);font-weight:500">${esc(name)}</span>${badge ? `<span class="fit-badge ok">${esc(badge)}</span>` : ""}</div>
<div class="li-sub">${esc(desc)}</div></div>
<a href="${esc(url)}" target="_blank" rel="noopener"><button class="ghost">Öffnen</button></a>
</div>`;
}
function render() {
const c = $(".view[data-view='connect']"); if (!c) return;
const url = baseUrl();
const ids = models().map(m => m.name);
const firstModel = ids[0] || "coder";
const visionModel = models().find(m => m.meta?.caps?.includes("Bild"))?.name;
c.innerHTML = `
<div class="pagehead"><div>
<h1>Verbinden</h1>
<div class="sub">Binde deine lokalen Modelle in IDEs, Agenten & Tools ein — getestet, mit Copy-Paste-Config.</div></div></div>
<div class="card">
<div class="card-h"><h3>Verbindung zur Engine</h3></div>
<div class="card-sub">Alle Tools nutzen diese OpenAI-kompatible Adresse. Das Auto-Swapping lädt das passende Modell je Anfrage automatisch.</div>
${field("Basis-URL (OpenAI-kompatibel)", url)}
${behindProxy() ? `<div class="alert warn" style="margin:0 0 12px"><span class="a-dot"></span><span>
Du rufst Mission Control offenbar über einen <b>Proxy/Namen</b> auf (NPM o.ä.). Die <b>Engine-API</b> läuft aber
direkt am Bosgame auf <b>Port 8080 ohne HTTPS</b> — ein Proxy davor stört nur. Trag in deinen Tools die
<b>rohe LAN-Adresse</b> ein: <code>http://&lt;Bosgame-IP&gt;:8080/v1</code> (z.B. <code>http://192.168.178.151:8080/v1</code>),
<b>nicht</b> den HTTPS-Namen.</span></div>` : ""}
<div class="btn-row"><button class="primary" id="cn-test">Verbindung testen</button></div>
<div id="cn-test-result" style="margin-top:12px"></div>
</div>
<div class="card">
<div class="card-h"><h3>Bilder einbinden (Vision)</h3></div>
<div class="card-sub">Für Fehleranalyse per Screenshot brauchst du ein Vision-Modell.</div>
<div id="cn-vision"></div>
</div>
<div class="card">
<div class="card-h"><h3>Empfohlene Tools (Juni 2026)</h3></div>
<div class="card-sub">Diese verbinden sich nahtlos mit deinem Stack. Einrichtungs-Configs findest du darunter.</div>
${toolRow("Zed", "Schneller nativer Editor mit eingebautem KI-Agenten — verbindet sich direkt mit deinem Stack.", "https://zed.dev", "Empfohlen")}
${toolRow("OpenCode", "Open-Source-Coding-Agent (MIT) — Terminal, Desktop & IDE. Gute Alternative.", "https://opencode.ai")}
${toolRow("Cline", "Bestes Erlebnis in VS Code, Bring-Your-Own-Key, Mensch-im-Loop.", "https://cline.bot")}
${toolRow("Continue.dev", "Feinkörnige Kontrolle + bestes MCP-Ökosystem für Werkzeuge.", "https://continue.dev")}
<div class="hint" style="margin-top:12px">Diese Tools sind <b>Agenten</b>: Sie lassen dein Modell selbst Dateien ändern und Befehle ausführen.
Noch unklar, was <b>Agent</b>, <b>Skill</b> oder <b>MCP</b> heißt?
<a href="#" onclick="event.preventDefault();window.cnGo('guides')">Im Guide von Grund auf erklärt →</a></div>
</div>
<div class="card" style="padding:0;overflow:hidden">
<details class="guide-acc" open>
<summary>Zed — settings.json (empfohlen)</summary>
<div class="acc-body">
<p>Zed konfigurierst du über seine <b>settings.json</b> (in Zed: <code>Cmd/Strg + ,</code>). Füge diesen Block ein —
er meldet deine Modelle als eigenen Anbieter „bosgame" an:</p>
<div style="position:relative;margin-top:12px">
<button class="ghost" style="position:absolute;top:8px;right:8px;z-index:1" onclick="window.mcCopyCode(this)">Kopieren</button>
<div class="log" style="max-height:none"><code>${esc(zedConfig(url))}</code></div>
</div>
<p style="margin-top:12px"><b>API-Key:</b> Zed speichert Keys <u>nicht</u> in der settings.json. Öffne den
Agent-Panel → Anbieter <b>bosgame</b> → trag irgendeinen Wert ein (z.B. <code>local</code>) — llama-swap prüft ihn nicht.
Alternativ als Umgebungsvariable <code>BOSGAME_API_KEY=local</code>.</p>
<div class="hint" style="margin-top:8px">Danach im Agent-Panel oben das Modell <b>bosgame / ${esc(firstModel)}</b> wählen.
Das Auto-Swapping lädt das passende Modell selbst. ${behindProxy() ? "<b>Achtung:</b> in <code>api_url</code> die LAN-IP des Bosgame nutzen (siehe Warnung oben), nicht den Proxy-Namen." : ""}</div>
</div>
</details>
<details class="guide-acc">
<summary>OpenCode — Config-Datei (JSON)</summary>
<div class="acc-body">
<p>OpenCode richtest du nicht über Felder ein, sondern über eine <b>Config-Datei</b>. Lege sie hier an:</p>
<p class="mono-sm" style="line-height:1.6">Windows: <code>%USERPROFILE%\\.config\\opencode\\opencode.json</code><br>
(oder im Projektordner als <code>opencode.json</code>)</p>
<div style="position:relative;margin-top:12px">
<button class="ghost" style="position:absolute;top:8px;right:8px;z-index:1" onclick="window.mcCopyCode(this)">Kopieren</button>
<div class="log" style="max-height:none"><code>${esc(openCodeConfig(url, ids))}</code></div>
</div>
<p style="margin-top:12px">Danach in OpenCode <code>/models</code> tippen und <b>llama-swap/${esc(firstModel)}</b> wählen. Das Auto-Swapping lädt das passende Modell von selbst.</p>
<div class="hint" style="margin-top:8px"><b>Fehler „Connect to API could not be established"?</b> Dann zeigt die <code>baseURL</code> ins Leere. Sie muss auf die <b>LAN-IP des Bosgame</b> zeigen (oben: <code>${esc(url)}</code>) — niemals <code>localhost</code> oder <code>127.0.0.1</code>, denn das wäre dein eigener PC, nicht der Server.</div>
</div>
</details>
<details class="guide-acc">
<summary>Cline / Cursor</summary>
<div class="acc-body">
<p>Provider „OpenAI Compatible" wählen:</p>
${field("Basis-URL", url)}
${field("Modell-ID", firstModel)}
</div>
</details>
<details class="guide-acc">
<summary>OpenWebUI</summary>
<div class="acc-body">
<p>Settings → Admin → Connections → neue OpenAI-Verbindung:</p>
${field("Basis-URL", url)}
${field("API-Key", "dummy-key")}
</div>
</details>
</div>`;
$("#cn-test").addEventListener("click", testConnection);
renderVision(url, visionModel);
}
function renderVision(url, visionModel) {
const el = $("#cn-vision"); if (!el) return;
if (visionModel) {
const snippet = `curl ${url}/chat/completions \\
-H "Content-Type: application/json" \\
-d '{
"model": "${visionModel}",
"messages": [{"role":"user","content":[
{"type":"text","text":"Was ist auf dem Bild falsch?"},
{"type":"image_url","image_url":{"url":"data:image/png;base64,<DEIN_BILD>"}}
]}]
}'`;
el.innerHTML = `${okChip(`Vision-Modell „${visionModel}" bereit`)}
<div style="position:relative;margin-top:12px">
<button class="ghost" style="position:absolute;top:8px;right:8px;z-index:1" onclick="window.mcCopyCode(this)">Kopieren</button>
<div class="log" style="max-height:none"><code>${esc(snippet)}</code></div>
</div>`;
} else {
el.innerHTML = `<div class="empty-c" style="padding:24px">
<div class="e-t">Noch kein Vision-Modell installiert</div>
<div class="e-s">Im Cookbook unter „Bilder verstehen" mit einem Klick einrichten.</div>
<button class="primary" style="margin-top:14px" onclick="window.cnGo('cookbook')">Zum Cookbook</button>
</div>`;
}
}
async function testConnection() {
const res = $("#cn-test-result"); res.innerHTML = `<div class="hint">Teste Verbindung…</div>`;
try {
const r = await api("/api/integration/test");
res.innerHTML = r.ok
? okChip(`Funktioniert — ${r.models.length} Modell(e): ${r.models.join(", ")}`)
: `<div class="alert warn" style="margin:0"><span class="a-dot"></span><span>Keine Verbindung zur Engine: ${esc(r.error)}</span></div>`;
} catch (e) { res.innerHTML = `<div class="alert" style="margin:0"><span class="a-dot"></span><span>${esc(e.message)}</span></div>`; }
}
function mount() { render(); }
function onStatus(s) {
S = s;
const sig = (s?.models || []).map(m => m.name).join(",") + "|" + !!s;
if (sig !== lastSig) { lastSig = sig; render(); }
}
export default { id: "connect", mount, onStatus };