diff --git a/static/js/panels/connect.js b/static/js/panels/connect.js index ae71ec5..0971591 100644 --- a/static/js/panels/connect.js +++ b/static/js/panels/connect.js @@ -1,76 +1,37 @@ -// connect.js — „Verbinden" (v4): IDEs/Agenten anbinden, Verbindung testen, Bild/Vision-Flow. +// connect.js — „Verbinden" (v5): Wizard-Style, Merge-sichere Configs, Auto-Swap erklärt. -import { api } from "../core/api.js"; -import { $, esc, icon } from "../core/ui.js"; +import { api, getToken } from "../core/api.js"; +import { $, esc, icon, toast } from "../core/ui.js"; let S = null; let lastSig = ""; +let selectedTool = "zed"; +let selectedModelIds = []; +window.mcCopyCode = btn => { + const code = btn.parentElement.querySelector("code,pre"); + navigator.clipboard?.writeText(code?.textContent || ""); + const o = btn.textContent; btn.textContent = "Kopiert!"; setTimeout(() => (btn.textContent = o), 1400); +}; 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); + const o = btn.textContent; btn.textContent = "Kopiert!"; setTimeout(() => (btn.textContent = o), 1400); }; window.cnGo = v => document.querySelector(`.nav-item[data-view="${v}"]`)?.click(); +window.cnSelectTool = t => { selectedTool = t; renderGuide(); }; +window.cnToggleModel = id => { + if (selectedModelIds.includes(id)) selectedModelIds = selectedModelIds.filter(x => x !== id); + else selectedModelIds.push(id); + renderGuide(); +}; -// 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:// 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 models() { return (S?.models || []).filter(m => !m.incomplete); } +function activeIds() { + return selectedModelIds.length ? selectedModelIds : models().map(m => m.name); } function field(label, value) { @@ -80,117 +41,203 @@ function field(label, value) { `; } -const okChip = txt => `${icon("check")}${esc(txt)}`; -function toolRow(name, desc, url, badge) { - return `
-
-
${esc(name)}${badge ? `${esc(badge)}` : ""}
-
${esc(desc)}
- +const okChip = txt => + `${icon("check")}${esc(txt)}`; + +// ---- Merge-sichere Config-Snippets ---- + +function zedMergeBlock(url, ids) { + const ms = ids.length + ? models().filter(m => ids.includes(m.name)) + : (models().length ? models() : [{ name: "coder", meta: {} }]); + const entries = ms.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 openCodeMergeBlock(url, ids) { + const list = ids.length ? ids : (models().length ? models().map(m => m.name) : ["coder"]); + const modelMap = list.map(id => ` "${id}": { "name": "${id}" }`).join(",\n"); + return `"provider": { + "llama-swap": { + "npm": "@ai-sdk/openai-compatible", + "name": "Bosgame (llama-swap)", + "options": { + "baseURL": "${url}", + "apiKey": "local" + }, + "models": { +${modelMap} + } + } +}`; +} + +// ---- Render-Abschnitte ---- + +function renderConnectionCard(url) { + return `
+

1 · Verbindung prüfen

+
Stelle sicher, dass die LLM-Engine erreichbar ist, bevor du Tools konfigurierst.
+ ${field("Engine-Adresse (für alle Tools)", url)} + ${behindProxy() ? `
+ Du öffnest Mission Control über einen Proxy/Namen (HTTPS o.ä.). Die Engine läuft auf Port 8080 ohne HTTPS direkt am Bosgame. + Trag in deinen Tools die LAN-IP ein: http://192.168.178.151:8080/v1 — nicht den Proxy-Namen.
` : ""} +
+ + +
`; } -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 = ` -
-

Verbinden

-
Binde deine lokalen Modelle in IDEs, Agenten & Tools ein — getestet, mit Copy-Paste-Config.
- -
-

Verbindung zur Engine

-
Alle Tools nutzen diese OpenAI-kompatible Adresse. Das Auto-Swapping lädt das passende Modell je Anfrage automatisch.
- ${field("Basis-URL (OpenAI-kompatibel)", url)} - ${behindProxy() ? `
- Du rufst Mission Control offenbar über einen Proxy/Namen auf (NPM o.ä.). Die Engine-API läuft aber - direkt am Bosgame auf Port 8080 ohne HTTPS — ein Proxy davor stört nur. Trag in deinen Tools die - rohe LAN-Adresse ein: http://<Bosgame-IP>:8080/v1 (z.B. http://192.168.178.151:8080/v1), - nicht den HTTPS-Namen.
` : ""} -
-
+function renderAutoSwapCard() { + return `
+

Wie funktioniert das Auto-Swap?

+
+
+ +
+
Kein manuelles Starten nötig
+
Wenn du in Zed oder OpenCode „coder" auswählst und eine Anfrage sendest, + lädt llama-swap das Modell automatisch — in ca. 2–5 Sekunden. Du siehst es hier im Dashboard unter „Aktives Modell".
+
+
+
+ 🔄 +
+
Modelle wechseln sich automatisch ab
+
Rufst du ein anderes Modell auf, entlädt llama-swap das aktuelle (nach TTL-Ablauf) + und lädt das neue. Es läuft immer nur eines gleichzeitig — llama-swap verwaltet das komplett selbst.
+
+
+
+ 🧠 +
+
Zwei Modelle gleichzeitig aktiv halten
+
Du hast 124 GB RAM — genug für z.B. Scout 8B (~5 GB) dauerhaft geladen + ein + größeres Modell das swappt. Dafür beim Scout im Modelle-Tab → Konfigurieren die TTL auf 99999 setzen + (= nie automatisch entladen). Das kleine Modell beantwortet schnelle Fragen sofort, ohne jede Wartezeit.
+
+
- -
-

Bilder einbinden (Vision)

-
Für Fehleranalyse per Screenshot brauchst du ein Vision-Modell.
-
-
- -
-

Empfohlene Tools (Juni 2026)

-
Diese verbinden sich nahtlos mit deinem Stack. Einrichtungs-Configs findest du darunter.
- ${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")} -
Diese Tools sind Agenten: Sie lassen dein Modell selbst Dateien ändern und Befehle ausführen. - Noch unklar, was Agent, Skill oder MCP heißt? - Im Guide von Grund auf erklärt →
-
- -
-
- Zed — settings.json (empfohlen) -
-

Zed konfigurierst du über seine settings.json (in Zed: Cmd/Strg + ,). Füge diesen Block ein — - er meldet deine Modelle als eigenen Anbieter „bosgame" an:

-
- -
${esc(zedConfig(url))}
-
-

API-Key: Zed speichert Keys nicht in der settings.json. Öffne den - Agent-Panel → Anbieter bosgame → trag irgendeinen Wert ein (z.B. local) — llama-swap prüft ihn nicht. - Alternativ als Umgebungsvariable BOSGAME_API_KEY=local.

-
Danach im Agent-Panel oben das Modell bosgame / ${esc(firstModel)} wählen. - Das Auto-Swapping lädt das passende Modell selbst. ${behindProxy() ? "Achtung: in api_url die LAN-IP des Bosgame nutzen (siehe Warnung oben), nicht den Proxy-Namen." : ""}
-
-
-
- OpenCode — Config-Datei (JSON) -
-

OpenCode richtest du nicht über Felder ein, sondern über eine Config-Datei. Lege sie hier an:

-

Windows: %USERPROFILE%\\.config\\opencode\\opencode.json
- (oder im Projektordner als opencode.json)

-
- -
${esc(openCodeConfig(url, ids))}
-
-

Danach in OpenCode /models tippen und llama-swap/${esc(firstModel)} wählen. Das Auto-Swapping lädt das passende Modell von selbst.

-
Fehler „Connect to API could not be established"? Dann zeigt die baseURL ins Leere. Sie muss auf die LAN-IP des Bosgame zeigen (oben: ${esc(url)}) — niemals localhost oder 127.0.0.1, denn das wäre dein eigener PC, nicht der Server.
-
-
-
- Cline / Cursor -
-

Provider „OpenAI Compatible" wählen:

- ${field("Basis-URL", url)} - ${field("Modell-ID", firstModel)} -
-
-
- OpenWebUI -
-

Settings → Admin → Connections → neue OpenAI-Verbindung:

- ${field("Basis-URL", url)} - ${field("API-Key", "dummy-key")} -
-
-
`; - - $("#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 \\ +function renderModelSelector() { + const ms = models(); + if (!ms.length) return ""; + const chips = ms.map(m => + `${esc(m.name)}` + ).join(" "); + const note = selectedModelIds.length + ? `${selectedModelIds.length} von ${ms.length} ausgewählt` + : "alle werden eingeschlossen"; + return `
+

2 · Modelle auswählen

${note}
+
Wähle, welche Modelle im Config-Snippet erscheinen sollen. Standardmäßig alle.
+
${chips}
+ ${selectedModelIds.length ? `
Auswahl aufheben (alle)
` : ""} +
`; +} +window.cnClearModels = () => { selectedModelIds = []; renderGuide(); }; + +function toolBtn(id, label, desc) { + const active = selectedTool === id; + return ``; +} + +function renderToolPicker() { + return `
+

3 · Tool wählen

+
Für welches Werkzeug brauchst du die Konfiguration?
+
+ ${toolBtn("zed", "Zed", "Schneller nativer Editor — Empfohlen")} + ${toolBtn("opencode", "OpenCode", "Open-Source Coding-Agent (Terminal/Desktop)")} + ${toolBtn("cline", "Cline / Cursor", "VS Code Extension, Bring-Your-Own-Key")} + ${toolBtn("openwebui", "OpenWebUI", "Browser-Chat-Interface")} +
+
`; +} + +function renderConfigCard(url) { + const ids = activeIds(); + const firstModel = ids[0] || "coder"; + let content = ""; + + if (selectedTool === "zed") { + const block = zedMergeBlock(url, ids); + content = ` +

Öffne die Zed-Settings mit Cmd/Strg + , und füge diesen Block in deine bestehende + settings.json ein. Falls bereits ein "bosgame"-Eintrag existiert: vorher löschen.

+
+ Nur diesen Block einfügen, nicht die ganze Datei ersetzen — sonst gehen andere Einstellungen verloren. +
+
+ +
${esc(block)}
+
+

API-Key: Öffne den Agent-Panel → Anbieter bosgame → trag irgendeinen Wert ein (z.B. local). + Alternativ BOSGAME_API_KEY=local als Umgebungsvariable.

+
Danach im Agent-Panel das Modell bosgame / ${esc(firstModel)} auswählen. Das Auto-Swap lädt es beim ersten Request.
`; + } else if (selectedTool === "opencode") { + const block = openCodeMergeBlock(url, ids); + content = ` +

Datei anlegen oder öffnen:

+

Windows: %USERPROFILE%\\.config\\opencode\\opencode.json
+ Linux/Mac: ~/.config/opencode/opencode.json
+ Oder im Projektordner: opencode.json

+
+ Nur den "provider"-Block einfügen oder mergen. + Falls bereits ein "llama-swap"-Eintrag vorhanden ist: vorher löschen, sonst erscheinen Modelle doppelt. +
+
+ +
${esc(block)}
+
+

Danach in OpenCode /modelsllama-swap/${esc(firstModel)} wählen.

+
Statt alles zu ersetzen: nur den provider-Block in deine bestehende Config einfügen — so bleiben andere Anbieter erhalten.
`; + } else if (selectedTool === "cline") { + content = ` +

In Cline/Cursor den Provider „OpenAI Compatible" wählen und folgende Felder eintragen:

+ ${field("Basis-URL (OpenAI Compatible)", url)} + ${field("Modell-ID", firstModel)} +
API-Key: ein beliebiger Wert (z.B. local) — llama-swap prüft ihn nicht.
`; + } else if (selectedTool === "openwebui") { + content = ` +

Settings → Admin Panel → Connections → neue OpenAI-Verbindung:

+ ${field("Basis-URL", url)} + ${field("API-Key", "dummy-key")} +
Nach dem Speichern erscheinen die Modelle automatisch in der Modell-Auswahl.
`; + } + + return `
+

4 · Config-Snippet

+ ${content} +
`; +} + +function renderVisionCard(url) { + const visionModel = models().find(m => m.meta?.caps?.includes("Bild"))?.name; + if (!visionModel) return ""; + const snippet = `curl ${url}/chat/completions \\ -H "Content-Type: application/json" \\ -d '{ "model": "${visionModel}", @@ -199,28 +246,56 @@ function renderVision(url, visionModel) { {"type":"image_url","image_url":{"url":"data:image/png;base64,"}} ]}] }'`; - el.innerHTML = `${okChip(`Vision-Modell „${visionModel}" bereit`)} -
- -
${esc(snippet)}
-
`; - } else { - el.innerHTML = `
-
Noch kein Vision-Modell installiert
-
Im Cookbook unter „Bilder verstehen" mit einem Klick einrichten.
- -
`; - } + return `
+

Bilder einbinden (Vision)

${okChip(`„${visionModel}" bereit`)}
+
Mit Vision-Modellen kannst du Screenshots oder Fehlerbilder direkt mitschicken.
+
+ +
${esc(snippet)}
+
+
`; +} + +function render() { + const c = $(".view[data-view='connect']"); if (!c) return; + const url = baseUrl(); + + c.innerHTML = ` +
+

Verbinden

+
Schritt für Schritt: KI-Modelle in deine Tools einbinden — getestet, erklärt, mit Copy-Paste-Config.
+
+ + ${renderConnectionCard(url)} + ${renderAutoSwapCard()} + ${renderModelSelector()} + ${renderToolPicker()} + ${renderConfigCard(url)} + ${renderVisionCard(url)}`; + + $("#cn-test")?.addEventListener("click", testConnection); +} + +function renderGuide() { + const url = baseUrl(); + const sel = $(".view[data-view='connect'] .card:nth-of-type(3)"); + if (!sel) { render(); return; } + // Nur die unteren 3 Karten neu rendern (Modell-Auswahl, Tool-Picker, Config) + const c = $(".view[data-view='connect']"); + const cards = c?.querySelectorAll(".card"); + if (!cards || cards.length < 3) { render(); return; } + // Einfachster Weg: komplett neu rendern (Scroll-Position bleibt wegen Hash-Routing ok) + render(); } async function testConnection() { - const res = $("#cn-test-result"); res.innerHTML = `
Teste Verbindung…
`; + const res = $("#cn-test-result"); res.innerHTML = `Teste…`; try { const r = await api("/api/integration/test"); res.innerHTML = r.ok - ? okChip(`Funktioniert — ${r.models.length} Modell(e): ${r.models.join(", ")}`) - : `
Keine Verbindung zur Engine: ${esc(r.error)}
`; - } catch (e) { res.innerHTML = `
${esc(e.message)}
`; } + ? okChip(`Verbunden — ${r.models.length} Modell(e): ${r.models.join(", ")}`) + : `✗ Keine Verbindung: ${esc(r.error)}`; + } catch (e) { res.innerHTML = `✗ ${esc(e.message)}`; } } function mount() { render(); }