diff --git a/Endpoints/AgentEndpoint.cs b/Endpoints/AgentEndpoint.cs
index 674e917..ab2b904 100644
--- a/Endpoints/AgentEndpoint.cs
+++ b/Endpoints/AgentEndpoint.cs
@@ -42,6 +42,10 @@ public static class AgentEndpoint
var avatarRaw = body.TryGetProperty("avatar", out var av) ? av.GetString() : null;
+ string customCommands = null;
+ if (body.TryGetProperty("customCommands", out var ccEl))
+ customCommands = ccEl.ValueKind == JsonValueKind.Null ? null : ccEl.GetRawText();
+
var agent = new AgentRecord
{
Name = body.TryGetProperty("name", out var n) ? n.GetString() ?? "Neuer Agent" : "Neuer Agent",
@@ -50,7 +54,8 @@ public static class AgentEndpoint
Model = body.TryGetProperty("model", out var m) ? m.GetString() ?? "sonnet" : "sonnet",
Persona = body.TryGetProperty("persona", out var p) ? p.GetString() : null,
Instructions = body.TryGetProperty("instructions", out var i) ? i.GetString() : null,
- ToolChoice = body.TryGetProperty("toolChoice", out var tc) ? tc.GetString() ?? "any" : "any"
+ ToolChoice = body.TryGetProperty("toolChoice", out var tc) ? tc.GetString() ?? "any" : "any",
+ CustomCommands = customCommands
};
agent = await db.CreateAgent(agent);
@@ -74,6 +79,8 @@ public static class AgentEndpoint
if (body.TryGetProperty("persona", out var p)) agent.Persona = p.GetString();
if (body.TryGetProperty("instructions", out var i)) agent.Instructions = i.GetString();
if (body.TryGetProperty("toolChoice", out var tc)) agent.ToolChoice = tc.GetString();
+ if (body.TryGetProperty("customCommands", out var cc))
+ agent.CustomCommands = cc.ValueKind == JsonValueKind.Null ? null : cc.GetRawText();
await db.UpdateAgent(agent);
agent = await db.GetAgent(agentId);
diff --git a/Models/AgentRecord.cs b/Models/AgentRecord.cs
index 783e36d..0f10068 100644
--- a/Models/AgentRecord.cs
+++ b/Models/AgentRecord.cs
@@ -11,6 +11,7 @@ public class AgentRecord
public string Persona { get; set; }
public string Instructions { get; set; }
public string ToolChoice { get; set; } // "any" oder "auto"
+ public string CustomCommands { get; set; }
public bool IsDeleted { get; set; }
public string CreatedAt { get; set; }
public string UpdatedAt { get; set; }
diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs
index 218b50b..0039c56 100644
--- a/Services/DatabaseService.cs
+++ b/Services/DatabaseService.cs
@@ -127,6 +127,7 @@ public class DatabaseService
TryAddColumn(conn, "messages", "attachment_count", "INTEGER NOT NULL DEFAULT 0");
TryAddColumn(conn, "messages", "attachment_names", "TEXT");
TryAddColumn(conn, "agents", "tool_choice", "TEXT DEFAULT 'any'");
+ TryAddColumn(conn, "agents", "custom_commands", "TEXT");
}
private void TryAddColumn(SqliteConnection conn, string table, string column, string type)
@@ -145,8 +146,8 @@ public class DatabaseService
agent.CreatedAt = now;
agent.UpdatedAt = now;
await conn.ExecuteAsync(@"
- INSERT INTO agents (agent_id, name, avatar, description, model, persona, instructions, tool_choice, is_deleted, created_at, updated_at)
- VALUES (@AgentId, @Name, @Avatar, @Description, @Model, @Persona, @Instructions, @ToolChoice, 0, @CreatedAt, @UpdatedAt)
+ INSERT INTO agents (agent_id, name, avatar, description, model, persona, instructions, tool_choice, custom_commands, is_deleted, created_at, updated_at)
+ VALUES (@AgentId, @Name, @Avatar, @Description, @Model, @Persona, @Instructions, @ToolChoice, @CustomCommands, 0, @CreatedAt, @UpdatedAt)
", agent);
return agent;
}
@@ -171,7 +172,8 @@ public class DatabaseService
agent.UpdatedAt = DateTime.UtcNow.ToString("o");
await conn.ExecuteAsync(@"
UPDATE agents SET name = @Name, avatar = @Avatar, description = @Description,
- model = @Model, persona = @Persona, instructions = @Instructions, tool_choice = @ToolChoice, updated_at = @UpdatedAt
+ model = @Model, persona = @Persona, instructions = @Instructions, tool_choice = @ToolChoice,
+ custom_commands = @CustomCommands, updated_at = @UpdatedAt
WHERE agent_id = @AgentId
", agent);
}
diff --git a/wwwroot/css/chat.css b/wwwroot/css/chat.css
index 576ce78..dcb8d18 100644
--- a/wwwroot/css/chat.css
+++ b/wwwroot/css/chat.css
@@ -27,6 +27,12 @@
display: none;
}
+.cmd-bar-label {
+ font-size: var(--font-size);
+ opacity: var(--semi-transparent);
+ white-space: nowrap;
+}
+
.custom-cmd-btn:hover {
background: var(--f3-bg);
border-color: var(--f3-border);
diff --git a/wwwroot/index.html b/wwwroot/index.html
index 5d8c9be..f96a669 100644
--- a/wwwroot/index.html
+++ b/wwwroot/index.html
@@ -130,6 +130,9 @@
Tool-Nutzung erzwingen (tool_choice: any)
+
+
+
@@ -219,6 +222,7 @@
+
diff --git a/wwwroot/js/commands-ui.js b/wwwroot/js/commands-ui.js
new file mode 100644
index 0000000..285c893
--- /dev/null
+++ b/wwwroot/js/commands-ui.js
@@ -0,0 +1,58 @@
+// ── Custom Commands UI (shared between session-page and agent-page) ──
+
+function createCommandRow(container, name, command, type) {
+ name = name || '';
+ command = command || '';
+ type = type || 'background';
+
+ const item = document.createElement('div');
+ item.className = 'custom-command-item';
+ item.innerHTML = `
+
+
+
+
+
+
+ `;
+ var typeSelect = item.querySelector('.cmd-type');
+ typeSelect.value = type;
+ typeSelect.addEventListener('change', function() {
+ var cmdInput = item.querySelector('.cmd-command');
+ cmdInput.placeholder = typeSelect.value === 'message' ? 'Nachricht...' : 'Befehl (z.B. ./deploy.sh)';
+ });
+ item.querySelector('.remove-cmd-btn').addEventListener('click', function() { item.remove(); });
+ container.appendChild(item);
+}
+
+function getCommandsFromUI(container) {
+ var items = container.querySelectorAll('.custom-command-item');
+ var commands = [];
+ for (var i = 0; i < items.length; i++) {
+ var item = items[i];
+ var name = item.querySelector('.cmd-name').value.trim();
+ var command = item.querySelector('.cmd-command').value.trim();
+ var type = item.querySelector('.cmd-type').value;
+ if (name || command) commands.push({ name: name, command: command, type: type });
+ }
+ return commands.length > 0 ? commands : null;
+}
+
+function loadCommandsIntoUI(container, customCommands) {
+ if (!customCommands) return;
+ try {
+ var cmds = typeof customCommands === 'string' ? JSON.parse(customCommands) : customCommands;
+ for (var i = 0; i < cmds.length; i++) {
+ var cmd = cmds[i];
+ createCommandRow(container, cmd.name || '', cmd.command || '', cmd.type || 'background');
+ }
+ } catch (e) { /* ignore parse errors */ }
+}
+
+function _escCmdAttr(str) {
+ return str.replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>');
+}
diff --git a/wwwroot/js/pages/agent-page.js b/wwwroot/js/pages/agent-page.js
index a9117fe..4c96720 100644
--- a/wwwroot/js/pages/agent-page.js
+++ b/wwwroot/js/pages/agent-page.js
@@ -28,6 +28,8 @@ registerPage('agent', {
const saveBtn = document.getElementById('agentSaveBtn');
const cancelBtn = document.getElementById('agentCancelBtn');
const deleteBtn = document.getElementById('agentDeleteBtn');
+ const commandsList = document.getElementById('agentCommandsList');
+ const addCommandBtn = document.getElementById('agentAddCommandBtn');
_agentAvatarData = null;
@@ -45,8 +47,14 @@ registerPage('agent', {
if (agent.avatar) {
avatarPreview.innerHTML = `

`;
}
+
+ // Custom Commands laden
+ loadCommandsIntoUI(commandsList, agent.customCommands);
}
+ // Befehl hinzufügen
+ addCommandBtn.addEventListener('click', () => createCommandRow(commandsList));
+
// Avatar Upload
avatarBtn.addEventListener('click', () => avatarInput.click());
avatarInput.addEventListener('change', () => {
@@ -95,7 +103,8 @@ registerPage('agent', {
persona: personaEl.value,
instructions: instructionsEl.value,
toolChoice: toolChoiceEl.checked ? 'any' : 'auto',
- avatar: _agentAvatarData
+ avatar: _agentAvatarData,
+ customCommands: getCommandsFromUI(commandsList)
};
if (isNew) {
diff --git a/wwwroot/js/pages/chat-page.js b/wwwroot/js/pages/chat-page.js
index 418262f..b9b236e 100644
--- a/wwwroot/js/pages/chat-page.js
+++ b/wwwroot/js/pages/chat-page.js
@@ -57,13 +57,44 @@ registerPage('chat', {
titleEl.textContent = session.name || 'Unbenannt';
}
- // Custom Command Buttons
+ // Custom Command Buttons (Agent + Session)
const cmdBar = document.getElementById('customCmdBar');
if (cmdBar) {
cmdBar.innerHTML = '';
- let cmds = [];
- try { cmds = JSON.parse(session.customCommands || '[]') || []; } catch(e) {}
- cmds.forEach(cmd => {
+ let agentCmds = [];
+ if (session.agentId) {
+ try {
+ const agent = await apiGetAgent(session.agentId);
+ if (agent && agent.customCommands) {
+ agentCmds = JSON.parse(agent.customCommands) || [];
+ }
+ } catch(e) {}
+ }
+ let sessionCmds = [];
+ try { sessionCmds = JSON.parse(session.customCommands || '[]') || []; } catch(e) {}
+
+ const allCmds = agentCmds.concat(sessionCmds);
+ const hasAgentCmds = agentCmds.length > 0;
+ const hasSessionCmds = sessionCmds.length > 0;
+ const hasBoth = hasAgentCmds && hasSessionCmds;
+
+ if (hasBoth) {
+ const sep1 = document.createElement('span');
+ sep1.className = 'cmd-bar-label';
+ sep1.textContent = 'Agent:';
+ cmdBar.appendChild(sep1);
+ }
+
+ let sessionSepAdded = false;
+ let cmdIndex = 0;
+ allCmds.forEach((cmd, idx) => {
+ if (hasBoth && !sessionSepAdded && idx === agentCmds.length) {
+ const sep2 = document.createElement('span');
+ sep2.className = 'cmd-bar-label';
+ sep2.textContent = 'Session:';
+ cmdBar.appendChild(sep2);
+ sessionSepAdded = true;
+ }
if (!cmd.name || !cmd.command) return;
const type = cmd.type || 'background';
const btn = document.createElement('button');
diff --git a/wwwroot/js/pages/session-page.js b/wwwroot/js/pages/session-page.js
index dd7b23c..7759b0e 100644
--- a/wwwroot/js/pages/session-page.js
+++ b/wwwroot/js/pages/session-page.js
@@ -3,47 +3,6 @@
let _sessionImageData = null;
let _sessionPasteHandler = null;
-function _createCommandRow(container, name = '', command = '', type = 'background') {
- const item = document.createElement('div');
- item.className = 'custom-command-item';
- item.innerHTML = `
-
-
-
-
-
-
- `;
- const typeSelect = item.querySelector('.cmd-type');
- typeSelect.value = type;
- typeSelect.addEventListener('change', () => {
- const cmdInput = item.querySelector('.cmd-command');
- cmdInput.placeholder = typeSelect.value === 'message' ? 'Nachricht...' : 'Befehl (z.B. ./deploy.sh)';
- });
- item.querySelector('.remove-cmd-btn').addEventListener('click', () => item.remove());
- container.appendChild(item);
-}
-
-function _escAttr(str) {
- return str.replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>');
-}
-
-function _getCommandsFromUI(container) {
- const items = container.querySelectorAll('.custom-command-item');
- const commands = [];
- for (const item of items) {
- const name = item.querySelector('.cmd-name').value.trim();
- const command = item.querySelector('.cmd-command').value.trim();
- const type = item.querySelector('.cmd-type').value;
- if (name || command) commands.push({ name, command, type });
- }
- return commands.length > 0 ? commands : null;
-}
-
registerPage('session', {
async init(params) {
const sessionId = params.id;
@@ -89,16 +48,7 @@ registerPage('session', {
}
// Custom Commands laden
- if (session.customCommands) {
- try {
- const cmds = typeof session.customCommands === 'string'
- ? JSON.parse(session.customCommands)
- : session.customCommands;
- for (const cmd of cmds) {
- _createCommandRow(commandsList, cmd.name || '', cmd.command || '', cmd.type || 'background');
- }
- } catch (e) { /* ignore parse errors */ }
- }
+ loadCommandsIntoUI(commandsList, session.customCommands);
} else {
titleEl.textContent = 'Neue Session';
deleteBtn.hidden = true;
@@ -106,7 +56,7 @@ registerPage('session', {
}
// Befehl hinzufügen
- addCommandBtn.addEventListener('click', () => _createCommandRow(commandsList));
+ addCommandBtn.addEventListener('click', () => createCommandRow(commandsList));
// Agents laden
const agents = await apiGetAgents();
@@ -173,7 +123,7 @@ registerPage('session', {
// Speichern
saveBtn.addEventListener('click', async () => {
- const commands = _getCommandsFromUI(commandsList);
+ const commands = getCommandsFromUI(commandsList);
const data = {
name: nameEl.value.trim() || 'Unbenannt',
description: descEl.value.trim(),