Generalize MCP servers, add model refresh button, add sonnet-5
- MCP: replace fixed single Unity field with a generic list of servers
({name, url, token?}); dynamic mcp-config + allowlist glob mcp__*<name>*
per entry; new mcp-servers-ui.js editor (no data migration)
- Models: frontend refresh button wired to existing POST /api/models/refresh,
reloads optgroups and restores selection
- Model: add sonnet-5 alias + dropdown option (bare "sonnet" stays 4.6)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
1032cd5c63
commit
5f7dec6448
14 files changed
+257
-50
No files matched your search
@@ -292,7 +292,7 @@ public static class ChatEndpoint
|
||||
|
||||
if (anthropic.IsCliAvailable)
|
||||
{
|
||||
await anthropic.StreamChatViaCli(res, ctx.ModelId, ctx.SystemPrompt, messages, ctx.ChainId, workspace: ctx.Workspace, allowUserChoice: ctx.Agent?.AllowUserChoice ?? true, planMode: body.PlanMode, ultracode: body.Ultracode, webSearch: !string.IsNullOrEmpty(ctx.WebSearchVersion), sessionId: sessionId, agentId: ctx.AgentId, remoteMgr: remoteMgr, unityMcpUrl: ctx.Session?.UnityMcpUrl, unityMcpToken: ctx.Session?.UnityMcpToken, useAutoContext: ctx.Agent?.UseAutoContext ?? true, effort: ctx.Effort, seedUsage: panelUsage, workCt: workCt, sseCt: sseCt);
|
||||
await anthropic.StreamChatViaCli(res, ctx.ModelId, ctx.SystemPrompt, messages, ctx.ChainId, workspace: ctx.Workspace, allowUserChoice: ctx.Agent?.AllowUserChoice ?? true, planMode: body.PlanMode, ultracode: body.Ultracode, webSearch: !string.IsNullOrEmpty(ctx.WebSearchVersion), sessionId: sessionId, agentId: ctx.AgentId, remoteMgr: remoteMgr, mcpServers: ctx.Session?.McpServers, useAutoContext: ctx.Agent?.UseAutoContext ?? true, effort: ctx.Effort, seedUsage: panelUsage, workCt: workCt, sseCt: sseCt);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -83,6 +83,7 @@ public static class SessionEndpoint
|
||||
s.PreparerAgentId,
|
||||
s.UnityMcpUrl,
|
||||
s.UnityMcpToken,
|
||||
s.McpServers,
|
||||
s.IsDeleted,
|
||||
s.CreatedAt,
|
||||
s.UpdatedAt,
|
||||
|
||||
@@ -28,6 +28,7 @@ public class SessionRecord
|
||||
public int MoaLayers { get; set; } = 2; // MoA: Anzahl Proposer-Runden vor der Synthese (Fusion ignoriert)
|
||||
public string UnityMcpUrl { get; set; }
|
||||
public string UnityMcpToken { get; set; }
|
||||
public string McpServers { get; set; } // JSON-Array benutzerdefinierter MCP-Server [{name,url,token}]
|
||||
public string CreatedAt { get; set; }
|
||||
public string UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ public partial class AnthropicService
|
||||
string sessionId = null,
|
||||
string agentId = null,
|
||||
RemoteConnectionManager remoteMgr = null,
|
||||
string unityMcpUrl = null,
|
||||
string unityMcpToken = null,
|
||||
string mcpServers = null,
|
||||
bool useAutoContext = true,
|
||||
string effort = null,
|
||||
TokenUsage seedUsage = null,
|
||||
@@ -74,7 +73,8 @@ public partial class AnthropicService
|
||||
var remoteConn = remoteMgr != null && !string.IsNullOrEmpty(sessionId) ? remoteMgr.GetBySessionId(sessionId) : null;
|
||||
var hasRemote = remoteConn != null;
|
||||
var remoteOs = remoteConn?.OsVersion ?? "";
|
||||
mcpConfigPath = GenerateMcpConfig(sessionId, agentId, hasRemote, unityMcpUrl, unityMcpToken, remoteOs, modelId, webSearch);
|
||||
var mcpServerList = ClaudeCliService.ParseMcpServers(mcpServers);
|
||||
mcpConfigPath = GenerateMcpConfig(sessionId, agentId, hasRemote, mcpServerList, remoteOs, modelId, webSearch);
|
||||
|
||||
// Python-Verfügbarkeit prüfen und ggf. warnen
|
||||
if (FindPythonCommand() == null)
|
||||
@@ -305,7 +305,7 @@ public partial class AnthropicService
|
||||
ultracode: ultracode,
|
||||
webSearch: webSearch,
|
||||
mcpConfigPath: mcpConfigPath,
|
||||
hasUnityMcp: !string.IsNullOrEmpty(unityMcpUrl),
|
||||
mcpServerNames: mcpServerList.Select(s => s.Name).ToList(),
|
||||
effort: effort,
|
||||
sessionId: sessionId,
|
||||
chainId: chainId,
|
||||
@@ -907,7 +907,7 @@ public partial class AnthropicService
|
||||
/// Generiert eine temporäre MCP-Config-Datei für die Claude CLI.
|
||||
/// Enthält den MCP-Server für remote_execute.
|
||||
/// </summary>
|
||||
private string GenerateMcpConfig(string sessionId, string agentId = null, bool hasRemote = false, string unityMcpUrl = null, string unityMcpToken = null, string remoteOs = null, string modelId = null, bool webSearch = false)
|
||||
private string GenerateMcpConfig(string sessionId, string agentId = null, bool hasRemote = false, List<McpServerDef> userMcpServers = null, string remoteOs = null, string modelId = null, bool webSearch = false)
|
||||
{
|
||||
// Port aus Konfiguration oder Default
|
||||
var port = 19000;
|
||||
@@ -946,25 +946,30 @@ public partial class AnthropicService
|
||||
if (pythonCmd == null)
|
||||
Console.WriteLine("[MCP] ⚠ Python nicht gefunden (python3/python/py) – MCP-Tools werden nicht funktionieren!");
|
||||
|
||||
// Unity MCP Server (pro Session konfigurierbar)
|
||||
// Claude CLI erfordert expliziten "type" – "http" für streamableHttp, "sse" für SSE
|
||||
if (!string.IsNullOrEmpty(unityMcpUrl))
|
||||
// Benutzerdefinierte MCP-Server (pro Session, URL-basiert: http/sse + optionaler Token).
|
||||
// Claude CLI erfordert expliziten "type" – "http" für streamableHttp, "sse" für SSE.
|
||||
// Namen sind in ParseMcpServers bereits gesäubert + von reservierten Namen befreit.
|
||||
if (userMcpServers != null)
|
||||
{
|
||||
// URL-basierte Typ-Erkennung: /sse → SSE-Transport, sonst HTTP (streamableHttp)
|
||||
var mcpType = unityMcpUrl.TrimEnd('/').EndsWith("/sse", StringComparison.OrdinalIgnoreCase) ? "sse" : "http";
|
||||
var unityConfig = new Dictionary<string, object>
|
||||
foreach (var srv in userMcpServers)
|
||||
{
|
||||
["type"] = mcpType,
|
||||
["url"] = unityMcpUrl
|
||||
};
|
||||
if (!string.IsNullOrEmpty(unityMcpToken))
|
||||
{
|
||||
unityConfig["headers"] = new Dictionary<string, string>
|
||||
if (srv == null || string.IsNullOrWhiteSpace(srv.Url)) continue;
|
||||
// URL-basierte Typ-Erkennung: /sse → SSE-Transport, sonst HTTP (streamableHttp)
|
||||
var mcpType = srv.Url.TrimEnd('/').EndsWith("/sse", StringComparison.OrdinalIgnoreCase) ? "sse" : "http";
|
||||
var srvConfig = new Dictionary<string, object>
|
||||
{
|
||||
["Authorization"] = $"Bearer {unityMcpToken}"
|
||||
["type"] = mcpType,
|
||||
["url"] = srv.Url
|
||||
};
|
||||
if (!string.IsNullOrEmpty(srv.Token))
|
||||
{
|
||||
srvConfig["headers"] = new Dictionary<string, string>
|
||||
{
|
||||
["Authorization"] = $"Bearer {srv.Token}"
|
||||
};
|
||||
}
|
||||
mcpServers[srv.Name] = srvConfig;
|
||||
}
|
||||
mcpServers["unity"] = unityConfig;
|
||||
}
|
||||
|
||||
// z.ai Web-Search-Prime-MCP: GLM-Modelle können Anthropics native WebSearch nicht nutzen.
|
||||
|
||||
@@ -68,6 +68,7 @@ Nur deine finale Text-Antwort wird weiterverwendet. Formuliere sie vollständig
|
||||
"sonnet" => "claude-sonnet-4-6",
|
||||
"sonnet-4-5" => "claude-sonnet-4-5",
|
||||
"sonnet-4-6" => "claude-sonnet-4-6",
|
||||
"sonnet-5" => "claude-sonnet-5",
|
||||
"opus" => "claude-opus-4-8",
|
||||
"opus-4-5" => "claude-opus-4-5",
|
||||
"opus-4-6" => "claude-opus-4-6",
|
||||
|
||||
@@ -11,6 +11,16 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Gott.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Benutzerdefinierter MCP-Server (pro Session). URL-basiert (http/sse), optionaler Bearer-Token.
|
||||
/// </summary>
|
||||
public class McpServerDef
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string Token { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service für die Kommunikation mit der Claude Code CLI.
|
||||
/// Verwendet die CLI als Backend für OAuth-basierte Chats (kein API-Key nötig).
|
||||
@@ -238,6 +248,61 @@ public class ClaudeCliService
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reservierte MCP-Server-Namen (gott-intern) – dürfen von benutzerdefinierten Servern
|
||||
/// nicht überschrieben werden, sonst würde die interne Config/Allowlist kollidieren.
|
||||
/// </summary>
|
||||
private static readonly HashSet<string> _reservedMcpNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"gott-remote", "web-search-prime"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Parst die in der Session hinterlegte MCP-Server-Liste (JSON-Array von {name,url,token}).
|
||||
/// Säubert die Namen (glob-/config-tauglich), filtert reservierte Namen, Duplikate und
|
||||
/// Einträge ohne URL. Wirft nie – ungültige Eingaben ergeben eine leere Liste.
|
||||
/// </summary>
|
||||
public static List<McpServerDef> ParseMcpServers(string json)
|
||||
{
|
||||
var result = new List<McpServerDef>();
|
||||
if (string.IsNullOrWhiteSpace(json)) return result;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
if (doc.RootElement.ValueKind != JsonValueKind.Array) return result;
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var el in doc.RootElement.EnumerateArray())
|
||||
{
|
||||
if (el.ValueKind != JsonValueKind.Object) continue;
|
||||
var rawName = el.TryGetProperty("name", out var n) && n.ValueKind == JsonValueKind.String ? n.GetString() : null;
|
||||
var url = el.TryGetProperty("url", out var u) && u.ValueKind == JsonValueKind.String ? u.GetString() : null;
|
||||
var token = el.TryGetProperty("token", out var t) && t.ValueKind == JsonValueKind.String ? t.GetString() : null;
|
||||
var name = SanitizeMcpName(rawName);
|
||||
if (string.IsNullOrEmpty(name) || string.IsNullOrWhiteSpace(url)) continue;
|
||||
if (_reservedMcpNames.Contains(name)) continue;
|
||||
if (!seen.Add(name)) continue; // doppelter Name → ignorieren
|
||||
result.Add(new McpServerDef { Name = name, Url = url.Trim(), Token = string.IsNullOrWhiteSpace(token) ? null : token.Trim() });
|
||||
}
|
||||
}
|
||||
catch { /* ungültiges JSON → leere Liste */ }
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduziert einen MCP-Server-Namen auf glob-/config-taugliche Zeichen (Buchstaben, Ziffern,
|
||||
/// '_' und '-'). Andere Zeichen (inkl. Leerzeichen) werden entfernt.
|
||||
/// </summary>
|
||||
private static string SanitizeMcpName(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw)) return null;
|
||||
var sb = new StringBuilder();
|
||||
foreach (var c in raw.Trim())
|
||||
{
|
||||
if (char.IsLetterOrDigit(c) || c == '_' || c == '-') sb.Append(c);
|
||||
}
|
||||
return sb.Length > 0 ? sb.ToString() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Streaming-Chat über die Claude CLI mit Event-basiertem Callback.
|
||||
/// Unterstützt Text-Streaming und Tool-Use Events.
|
||||
@@ -253,7 +318,7 @@ public class ClaudeCliService
|
||||
bool ultracode = false,
|
||||
bool webSearch = false,
|
||||
string mcpConfigPath = null,
|
||||
bool hasUnityMcp = false,
|
||||
List<string> mcpServerNames = null,
|
||||
string effort = null,
|
||||
string sessionId = null,
|
||||
string chainId = null,
|
||||
@@ -344,11 +409,16 @@ public class ClaudeCliService
|
||||
tools.Add("mcp__gott-remote__memory_write");
|
||||
tools.Add("mcp__gott-remote__memory_list");
|
||||
}
|
||||
// Unity MCP Tools erlauben (alle Tools vom unity MCP-Server)
|
||||
if (hasUnityMcp)
|
||||
// Benutzerdefinierte MCP-Server: alle Tools je Server erlauben.
|
||||
// Glob mcp__*<name>* ist bewusst locker (fängt Casing-/Präfix-Varianten ab,
|
||||
// z.B. Servername "unity" vs. tatsächlicher Tool-Präfix "UnityMCP").
|
||||
if (mcpServerNames != null)
|
||||
{
|
||||
tools.Add("mcp__unity__*");
|
||||
tools.Add("mcp__UnityMCP__*");
|
||||
foreach (var mcpName in mcpServerNames)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(mcpName))
|
||||
tools.Add($"mcp__*{mcpName}*");
|
||||
}
|
||||
}
|
||||
args.Add("--allowed-tools");
|
||||
args.Add(tools.Count > 0 ? string.Join(" ", tools) : "\"\"");
|
||||
|
||||
@@ -163,6 +163,7 @@ public partial class DatabaseService
|
||||
if (body.TryGetProperty("moaLayers", out var mol) && mol.ValueKind == System.Text.Json.JsonValueKind.Number) { sets.Add("moa_layers = @moaLayers"); p.Add("moaLayers", mol.GetInt32()); }
|
||||
if (body.TryGetProperty("unityMcpUrl", out var umu)) { sets.Add("unity_mcp_url = @unityMcpUrl"); p.Add("unityMcpUrl", umu.ValueKind == System.Text.Json.JsonValueKind.Null ? null : umu.GetString()); }
|
||||
if (body.TryGetProperty("unityMcpToken", out var umt)) { sets.Add("unity_mcp_token = @unityMcpToken"); p.Add("unityMcpToken", umt.ValueKind == System.Text.Json.JsonValueKind.Null ? null : umt.GetString()); }
|
||||
if (body.TryGetProperty("mcpServers", out var mcps)) { sets.Add("mcp_servers = @mcpServers"); p.Add("mcpServers", mcps.ValueKind == System.Text.Json.JsonValueKind.Null ? null : mcps.GetRawText()); }
|
||||
|
||||
await conn.ExecuteAsync($"UPDATE sessions SET {string.Join(", ", sets)} WHERE session_id = @sessionId", p);
|
||||
}
|
||||
|
||||
@@ -196,6 +196,7 @@ public partial class DatabaseService
|
||||
TryAddColumn(conn, "sessions", "preparer_agent_id", "TEXT");
|
||||
TryAddColumn(conn, "sessions", "unity_mcp_url", "TEXT");
|
||||
TryAddColumn(conn, "sessions", "unity_mcp_token", "TEXT");
|
||||
TryAddColumn(conn, "sessions", "mcp_servers", "TEXT");
|
||||
TryAddColumn(conn, "messages", "tag", "TEXT");
|
||||
TryAddColumn(conn, "agents", "effort", "TEXT NOT NULL DEFAULT ''");
|
||||
TryAddColumn(conn, "chains", "effort", "TEXT NOT NULL DEFAULT ''");
|
||||
|
||||
@@ -10,6 +10,17 @@
|
||||
margin-top: var(--box-spacing);
|
||||
}
|
||||
|
||||
.form-label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: var(--box-spacing);
|
||||
}
|
||||
|
||||
.form-label-row label {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.more {
|
||||
margin: var(--box-spacing) auto;
|
||||
cursor: pointer;
|
||||
@@ -214,6 +225,46 @@
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.mcp-servers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--badge-spacing);
|
||||
}
|
||||
|
||||
.mcp-server-item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mcp-server-fields {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: var(--badge-spacing);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mcp-server-fields .mcp-name {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.mcp-server-fields .mcp-url {
|
||||
flex: 4;
|
||||
}
|
||||
|
||||
.mcp-server-fields .mcp-token {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
.mcp-server-item .remove-mcp-btn {
|
||||
margin-left: var(--badge-spacing);
|
||||
color: var(--dangerous);
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.mcp-server-item .remove-mcp-btn:hover {
|
||||
background: var(--dangerous);
|
||||
color: var(--f1-bg);
|
||||
}
|
||||
|
||||
/* ── Checkbox ── */
|
||||
|
||||
label.checkbox-label {
|
||||
|
||||
+10
-5
@@ -172,10 +172,10 @@
|
||||
<label>Benutzerdefinierte Befehle</label>
|
||||
<div class="custom-commands" id="customCommandsList"></div>
|
||||
<button type="button" id="addCommandBtn" class="add-cmd-btn">+ Befehl hinzufügen</button>
|
||||
<label>Unity MCP URL <small>(optional)</small></label>
|
||||
<input type="text" id="sessionUnityMcpUrl" placeholder="z.B. http://localhost:8080" autocomplete="off">
|
||||
<label>Unity MCP Token <small>(optional)</small></label>
|
||||
<input type="password" id="sessionUnityMcpToken" placeholder="MCP-Server Token" autocomplete="off">
|
||||
<label>MCP-Server <small>(optional, URL-basiert)</small></label>
|
||||
<div class="mcp-servers" id="mcpServersList"></div>
|
||||
<button type="button" id="addMcpServerBtn" class="add-cmd-btn">+ MCP-Server hinzufügen</button>
|
||||
<small class="form-hint">Pro Eintrag: Name, URL (http/sse) und optionaler Token. Tools werden headless als <code>mcp__*<name>*</code> freigegeben.</small>
|
||||
<label>Telegram Bot</label>
|
||||
<div id="telegramBotSelectCard" class="agent-select-card">
|
||||
<div class="agent-select-empty"><i class="fa fa-paper-plane"></i> Kein Bot zugewiesen</div>
|
||||
@@ -218,10 +218,14 @@
|
||||
<div class="avatar-preview" id="avatarPreview">?</div>
|
||||
<label>Name</label>
|
||||
<input type="text" id="agentName" placeholder="Name des Agents">
|
||||
<label>Model</label>
|
||||
<div class="form-label-row">
|
||||
<label>Model</label>
|
||||
<button type="button" id="refreshModelsBtn" class="icon-btn" title="Modell-Listen der Provider neu laden"><i class="fa fa-refresh"></i></button>
|
||||
</div>
|
||||
<select id="agentModel">
|
||||
<option value="sonnet-4-5">sonnet-4-5</option>
|
||||
<option value="sonnet-4-6">sonnet-4-6</option>
|
||||
<option value="sonnet-5">sonnet-5</option>
|
||||
<option value="opus-4-5">opus-4-5</option>
|
||||
<option value="opus-4-6">opus-4-6</option>
|
||||
<option value="opus-4-7">opus-4-7</option>
|
||||
@@ -818,6 +822,7 @@
|
||||
<script src="js/archive.js"></script>
|
||||
<script src="js/router.js"></script>
|
||||
<script src="js/commands-ui.js"></script>
|
||||
<script src="js/mcp-servers-ui.js"></script>
|
||||
<script src="js/avatar-editor.js"></script>
|
||||
<script src="js/pages/dashboard.js"></script>
|
||||
<script src="js/pages/cronjob-page.js"></script>
|
||||
|
||||
@@ -252,6 +252,9 @@ function apiDeleteApiKey(id) { return _del('/api/admin/api-keys/' + id, 'Fehler
|
||||
function apiCheckApiKey(id) { return _post('/api/admin/api-keys/' + id + '/check', undefined, 'Fehler beim Prüfen'); }
|
||||
function apiCheckAllApiKeys() { return _post('/api/admin/api-keys/check-all', undefined, 'Fehler beim Prüfen'); }
|
||||
|
||||
// ── Models API ──
|
||||
function apiRefreshModels() { return _post('/api/models/refresh', undefined, 'Fehler beim Neu-Laden der Modelle'); }
|
||||
|
||||
// ── Key Scanner API (Admin) ──
|
||||
|
||||
function apiTriggerScan(mode) { return _post('/api/admin/api-keys/scan?mode=' + (mode || 'full'), undefined, 'Fehler beim Starten des Scans'); }
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// ── MCP-Server UI (Liste pro Session) ──
|
||||
// Spiegelt das Muster von commands-ui.js: dynamische Add/Remove-Zeilen, JSON-Persistenz.
|
||||
// Pro Eintrag: Name, URL (http/sse), optionaler Token.
|
||||
|
||||
function createMcpServerRow(container, name, url, token) {
|
||||
name = name || '';
|
||||
url = url || '';
|
||||
token = token || '';
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'mcp-server-item';
|
||||
item.innerHTML = `
|
||||
<div class="mcp-server-fields">
|
||||
<input type="text" class="mcp-name" placeholder="Name (z.B. UnityMCP)" value="${esc(name)}" autocomplete="off">
|
||||
<input type="text" class="mcp-url" placeholder="URL (z.B. http://localhost:8080)" value="${esc(url)}" autocomplete="off">
|
||||
<input type="password" class="mcp-token" placeholder="Token (optional)" value="${esc(token)}" autocomplete="off">
|
||||
</div>
|
||||
<button type="button" class="remove-mcp-btn icon-btn"><i class="fa fa-times"></i></button>
|
||||
`;
|
||||
item.querySelector('.remove-mcp-btn').addEventListener('click', function() { item.remove(); });
|
||||
container.appendChild(item);
|
||||
}
|
||||
|
||||
function getMcpServersFromUI(container) {
|
||||
var items = container.querySelectorAll('.mcp-server-item');
|
||||
var servers = [];
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
var name = item.querySelector('.mcp-name').value.trim();
|
||||
var url = item.querySelector('.mcp-url').value.trim();
|
||||
var token = item.querySelector('.mcp-token').value.trim();
|
||||
// Nur vollständige Einträge (Name + URL) übernehmen.
|
||||
if (name && url) servers.push({ name: name, url: url, token: token || null });
|
||||
}
|
||||
return servers.length > 0 ? servers : null;
|
||||
}
|
||||
|
||||
function loadMcpServersIntoUI(container, mcpServers) {
|
||||
if (!mcpServers) return;
|
||||
try {
|
||||
var list = typeof mcpServers === 'string' ? JSON.parse(mcpServers) : mcpServers;
|
||||
if (!Array.isArray(list)) return;
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var s = list[i];
|
||||
createMcpServerRow(container, s.name || '', s.url || '', s.token || '');
|
||||
}
|
||||
} catch (e) { /* ignore parse errors */ }
|
||||
}
|
||||
@@ -23,12 +23,6 @@ registerPage('agent', {
|
||||
const nameEl = document.getElementById('agentName');
|
||||
const descEl = document.getElementById('agentDescription');
|
||||
const modelEl = document.getElementById('agentModel');
|
||||
const nvidiaOptgroup = document.getElementById('nvidiaOptgroup');
|
||||
const pollinationsOptgroup = document.getElementById('pollinationsOptgroup');
|
||||
const geminiOptgroup = document.getElementById('geminiOptgroup');
|
||||
const openaiOptgroup = document.getElementById('openaiOptgroup');
|
||||
const minimaxOptgroup = document.getElementById('minimaxOptgroup');
|
||||
const glmOptgroup = document.getElementById('glmOptgroup');
|
||||
|
||||
// Modelle dynamisch laden
|
||||
const loadProviderModels = async (url, prefix, optgroup) => {
|
||||
@@ -51,14 +45,39 @@ registerPage('agent', {
|
||||
}
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
loadProviderModels('/api/models/openai', 'openai', openaiOptgroup),
|
||||
loadProviderModels('/api/models/nvidia', 'nvidia', nvidiaOptgroup),
|
||||
loadProviderModels('/api/models/pollinations', 'pollinations', pollinationsOptgroup),
|
||||
loadProviderModels('/api/models/gemini', 'gemini', geminiOptgroup),
|
||||
loadProviderModels('/api/models/minimax', 'minimax', minimaxOptgroup),
|
||||
loadProviderModels('/api/models/glm', 'glm', glmOptgroup)
|
||||
// Optgroups bei jedem Aufruf frisch abfragen (nach dem ersten Laden können leere entfernt worden sein)
|
||||
const loadAllModels = () => Promise.all([
|
||||
loadProviderModels('/api/models/openai', 'openai', document.getElementById('openaiOptgroup')),
|
||||
loadProviderModels('/api/models/nvidia', 'nvidia', document.getElementById('nvidiaOptgroup')),
|
||||
loadProviderModels('/api/models/pollinations', 'pollinations', document.getElementById('pollinationsOptgroup')),
|
||||
loadProviderModels('/api/models/gemini', 'gemini', document.getElementById('geminiOptgroup')),
|
||||
loadProviderModels('/api/models/minimax', 'minimax', document.getElementById('minimaxOptgroup')),
|
||||
loadProviderModels('/api/models/glm', 'glm', document.getElementById('glmOptgroup'))
|
||||
]);
|
||||
|
||||
await loadAllModels();
|
||||
|
||||
// Refresh-Button: Modelle live von den Providern neu laden (Backend erneuert seinen Cache)
|
||||
const refreshModelsBtn = document.getElementById('refreshModelsBtn');
|
||||
if (refreshModelsBtn) {
|
||||
refreshModelsBtn.addEventListener('click', async () => {
|
||||
const origHtml = refreshModelsBtn.innerHTML;
|
||||
refreshModelsBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i>';
|
||||
refreshModelsBtn.disabled = true;
|
||||
const prevValue = modelEl.value;
|
||||
try {
|
||||
await apiRefreshModels();
|
||||
await loadAllModels();
|
||||
modelEl.value = prevValue; // vorherige Auswahl wiederherstellen (falls noch vorhanden)
|
||||
showToast('Modell-Listen neu geladen');
|
||||
} catch (err) {
|
||||
showToast(err.message || 'Fehler beim Neu-Laden der Modelle', 'error');
|
||||
} finally {
|
||||
refreshModelsBtn.innerHTML = origHtml;
|
||||
refreshModelsBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
const allowUserChoiceEl = document.getElementById('agentAllowUserChoice');
|
||||
const autoCommitEl = document.getElementById('agentAutoCommit');
|
||||
const effortEl = document.getElementById('agentEffort');
|
||||
|
||||
@@ -38,8 +38,8 @@ registerPage('session', {
|
||||
const deleteBtn = document.getElementById('sessionDeleteBtn');
|
||||
const commandsList = document.getElementById('customCommandsList');
|
||||
const addCommandBtn = document.getElementById('addCommandBtn');
|
||||
const unityMcpUrlEl = document.getElementById('sessionUnityMcpUrl');
|
||||
const unityMcpTokenEl = document.getElementById('sessionUnityMcpToken');
|
||||
const mcpServersList = document.getElementById('mcpServersList');
|
||||
const addMcpServerBtn = document.getElementById('addMcpServerBtn');
|
||||
const ownerRow = document.getElementById('sessionOwnerRow');
|
||||
const userSelectCard = document.getElementById('sessionUserSelectCard');
|
||||
const userSelect = document.getElementById('sessionUserSelect');
|
||||
@@ -64,9 +64,8 @@ registerPage('session', {
|
||||
// Custom Commands laden
|
||||
loadCommandsIntoUI(commandsList, session.customCommands);
|
||||
|
||||
// Unity MCP
|
||||
unityMcpUrlEl.value = session.unityMcpUrl || '';
|
||||
unityMcpTokenEl.value = session.unityMcpToken || '';
|
||||
// MCP-Server
|
||||
loadMcpServersIntoUI(mcpServersList, session.mcpServers);
|
||||
} else {
|
||||
titleEl.textContent = 'Neue Session';
|
||||
deleteBtn.hidden = true;
|
||||
@@ -81,6 +80,9 @@ registerPage('session', {
|
||||
// Befehl hinzufügen
|
||||
addCommandBtn.addEventListener('click', () => createCommandRow(commandsList));
|
||||
|
||||
// MCP-Server hinzufügen
|
||||
addMcpServerBtn.addEventListener('click', () => createMcpServerRow(mcpServersList));
|
||||
|
||||
// Telegram-Section nur für existierende Sessions
|
||||
if (!isNew) {
|
||||
initSessionTelegram(sessionId, session);
|
||||
@@ -217,8 +219,7 @@ registerPage('session', {
|
||||
avatarScale: _sessionAvatarEditor.getScale(),
|
||||
avatarOffsetX: _sessionAvatarEditor.getOffsetX(),
|
||||
avatarOffsetY: _sessionAvatarEditor.getOffsetY(),
|
||||
unityMcpUrl: unityMcpUrlEl.value.trim() || null,
|
||||
unityMcpToken: unityMcpTokenEl.value.trim() || null
|
||||
mcpServers: getMcpServersFromUI(mcpServersList)
|
||||
};
|
||||
|
||||
// Session einem anderen Benutzer zuweisen. Wechselt der Besitzer tatsächlich,
|
||||
|
||||
Reference in new issue
Block a user