2a60def469
- routers/integration.py: GET /api/integration/test ruft Engine /v1/models -> ok + Modell-Liste. - connect.js: Verbindungstest-Button, Bild/Vision-Flow (erkennt Vision-Modell, sonst Cookbook-Hinweis, OpenAI-Vision-Snippet), Tool-Configs (OpenCode/Cline/Cursor/OpenWebUI) mit Copy. - Nav-Eintrag 'Verbinden' + View + main.js Registrierung. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
135 lines
5.5 KiB
JavaScript
135 lines
5.5 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();
|
|
|
|
function baseUrl() { return `${location.protocol}//${location.hostname}:8080/v1`; }
|
|
function models() { return S?.models || []; }
|
|
|
|
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 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)}
|
|
<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" style="padding:0;overflow:hidden">
|
|
<details class="guide-acc" open>
|
|
<summary>OpenCode (Windows)</summary>
|
|
<div class="acc-body">
|
|
<p>Lege in OpenCode einen OpenAI-kompatiblen Provider an:</p>
|
|
${field("Basis-URL", url)}
|
|
${field("Modell-ID", firstModel)}
|
|
${field("API-Key", "(dein Token oder leer lassen)")}
|
|
</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 };
|