const state = {
currentConfig: {},
currentServerId: null,
+ currentServerName: null,
+ servers: [],
availableProviders: [],
plugins: [],
tools: [],
@@ -1256,19 +1266,39 @@
const response = await fetch('/api/servers');
if (!response.ok) throw new Error('Failed to fetch servers');
const servers = await response.json();
+
+ // Store servers in state
+ state.servers = servers;
const list = document.getElementById('server-list');
if (!servers.length) {
list.textContent = 'No servers connected.';
state.currentServerId = null;
+ state.currentServerName = null;
+ updateServerNameDisplay();
} else {
- state.currentServerId = servers[0].id;
+ // Keep the current selection if it still exists, otherwise default to first
+ if (!state.currentServerId || !servers.find(s => s.id === state.currentServerId)) {
+ state.currentServerId = servers[0].id;
+ state.currentServerName = servers[0].name;
+ }
+
list.innerHTML = servers.map(server => `
-
+
${server.name}
${server.member_count} members - ${server.llm_provider || 'N/A'} (${server.llm_model || 'N/A'})
`).join('');
+
+ // Add click handlers to server items
+ list.querySelectorAll('.server-item').forEach(item => {
+ item.addEventListener('click', () => {
+ const serverId = item.getAttribute('data-server-id');
+ selectServer(serverId);
+ });
+ });
+
+ updateServerNameDisplay();
}
await loadConfig();
@@ -1278,6 +1308,33 @@
Marketplace Insights
}
}
+ async function selectServer(serverId) {
+ if (state.currentServerId === serverId) return; // Already selected
+
+ state.currentServerId = serverId;
+
+ // Update server name
+ const server = state.servers.find(s => s.id === serverId);
+ state.currentServerName = server ? server.name : null;
+
+ // Update visual selection
+ document.querySelectorAll('.server-item').forEach(item => {
+ item.classList.toggle('selected', item.getAttribute('data-server-id') === serverId);
+ });
+
+ updateServerNameDisplay();
+
+ // Load configuration for the selected server
+ await loadConfig();
+ }
+
+ function updateServerNameDisplay() {
+ const nameElement = document.getElementById('selected-server-name');
+ if (nameElement) {
+ nameElement.textContent = state.currentServerName ? `(${state.currentServerName})` : '';
+ }
+ }
+
async function loadConfig() {
try {
let config;
From 3a382f5da49ee0b2526a5a23f8e52fc41b1e5802 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 5 Nov 2025 04:52:26 +0000
Subject: [PATCH 3/6] Add test for per-server configuration isolation
- Verify each server can have independent LLM settings
- Test configuration updates don't affect other servers
- Validate database-level isolation
- All tests passing successfully
Co-authored-by: franktheglock <169854515+franktheglock@users.noreply.github.com>
---
tests/test_server_config.py | 149 ++++++++++++++++++++++++++++++++++++
1 file changed, 149 insertions(+)
create mode 100644 tests/test_server_config.py
diff --git a/tests/test_server_config.py b/tests/test_server_config.py
new file mode 100644
index 0000000..e2278b8
--- /dev/null
+++ b/tests/test_server_config.py
@@ -0,0 +1,149 @@
+"""
+Test script to verify per-server LLM configuration functionality.
+"""
+import os
+import sys
+import sqlite3
+from pathlib import Path
+
+# Add project root to path
+sys.path.insert(0, str(Path(__file__).parent.parent))
+
+from src.config.manager import ConfigManager
+
+
+def test_per_server_config():
+ """Test that each server can have independent configuration."""
+ print("\n" + "="*60)
+ print("Testing Per-Server Configuration")
+ print("="*60 + "\n")
+
+ # Create a test database in a temporary location
+ test_db = Path(__file__).parent / "test_bot.db"
+ if test_db.exists():
+ test_db.unlink()
+
+ # Initialize config manager with test database
+ config_manager = ConfigManager()
+ config_manager.db_path = test_db
+ config_manager._init_database()
+
+ # Simulate two different Discord servers
+ server1_id = "123456789"
+ server2_id = "987654321"
+
+ print("1. Testing independent server configurations...")
+
+ # Set configuration for Server 1
+ server1_config = {
+ 'llm_provider': 'openai',
+ 'llm_model': 'gpt-4',
+ 'temperature': 0.7,
+ 'max_tokens': 2048,
+ 'system_prompt': 'You are a helpful assistant for Server 1.',
+ 'enabled_tools': ['web_search', 'calculator']
+ }
+ config_manager.set_server_config(server1_id, server1_config)
+ print(f" ✅ Set config for Server 1 (ID: {server1_id})")
+
+ # Set configuration for Server 2
+ server2_config = {
+ 'llm_provider': 'anthropic',
+ 'llm_model': 'claude-3-opus-20240229',
+ 'temperature': 0.5,
+ 'max_tokens': 4096,
+ 'system_prompt': 'You are a professional assistant for Server 2.',
+ 'enabled_tools': ['web_search']
+ }
+ config_manager.set_server_config(server2_id, server2_config)
+ print(f" ✅ Set config for Server 2 (ID: {server2_id})")
+
+ print("\n2. Verifying configurations are isolated...")
+
+ # Retrieve and verify Server 1 configuration
+ retrieved_config1 = config_manager.get_server_config(server1_id)
+ assert retrieved_config1['llm_provider'] == 'openai', "Server 1 provider mismatch"
+ assert retrieved_config1['llm_model'] == 'gpt-4', "Server 1 model mismatch"
+ assert retrieved_config1['system_prompt'] == 'You are a helpful assistant for Server 1.', "Server 1 system prompt mismatch"
+ assert retrieved_config1['temperature'] == 0.7, "Server 1 temperature mismatch"
+ print(f" ✅ Server 1 config verified:")
+ print(f" Provider: {retrieved_config1['llm_provider']}")
+ print(f" Model: {retrieved_config1['llm_model']}")
+ print(f" System Prompt: {retrieved_config1['system_prompt'][:50]}...")
+
+ # Retrieve and verify Server 2 configuration
+ retrieved_config2 = config_manager.get_server_config(server2_id)
+ assert retrieved_config2['llm_provider'] == 'anthropic', "Server 2 provider mismatch"
+ assert retrieved_config2['llm_model'] == 'claude-3-opus-20240229', "Server 2 model mismatch"
+ assert retrieved_config2['system_prompt'] == 'You are a professional assistant for Server 2.', "Server 2 system prompt mismatch"
+ assert retrieved_config2['temperature'] == 0.5, "Server 2 temperature mismatch"
+ print(f" ✅ Server 2 config verified:")
+ print(f" Provider: {retrieved_config2['llm_provider']}")
+ print(f" Model: {retrieved_config2['llm_model']}")
+ print(f" System Prompt: {retrieved_config2['system_prompt'][:50]}...")
+
+ print("\n3. Testing configuration updates...")
+
+ # Update Server 1 configuration
+ server1_config['system_prompt'] = 'Updated system prompt for Server 1'
+ config_manager.set_server_config(server1_id, server1_config)
+
+ # Verify Server 1 was updated
+ updated_config1 = config_manager.get_server_config(server1_id)
+ assert updated_config1['system_prompt'] == 'Updated system prompt for Server 1', "Server 1 update failed"
+ print(f" ✅ Server 1 system prompt updated successfully")
+
+ # Verify Server 2 was NOT affected
+ unchanged_config2 = config_manager.get_server_config(server2_id)
+ assert unchanged_config2['system_prompt'] == 'You are a professional assistant for Server 2.', "Server 2 was incorrectly modified"
+ print(f" ✅ Server 2 remains unchanged (isolation verified)")
+
+ print("\n4. Testing database-level verification...")
+
+ # Direct database query to ensure isolation
+ with sqlite3.connect(test_db) as conn:
+ cursor = conn.cursor()
+ cursor.execute("SELECT server_id, llm_provider, llm_model, system_prompt FROM server_config")
+ rows = cursor.fetchall()
+
+ assert len(rows) == 2, f"Expected 2 server configs, found {len(rows)}"
+ print(f" ✅ Database contains exactly 2 server configurations")
+
+ for row in rows:
+ server_id, provider, model, prompt = row
+ if server_id == server1_id:
+ assert provider == 'openai', "DB: Server 1 provider mismatch"
+ assert model == 'gpt-4', "DB: Server 1 model mismatch"
+ print(f" ✅ Database record for Server 1 is correct")
+ elif server_id == server2_id:
+ assert provider == 'anthropic', "DB: Server 2 provider mismatch"
+ assert model == 'claude-3-opus-20240229', "DB: Server 2 model mismatch"
+ print(f" ✅ Database record for Server 2 is correct")
+
+ print("\n" + "="*60)
+ print("✅ All Tests Passed!")
+ print("="*60)
+ print("\nPer-server configuration is working correctly:")
+ print(" • Each server can have its own LLM provider")
+ print(" • Each server can have its own model")
+ print(" • Each server can have its own system prompt")
+ print(" • Each server can have its own temperature/max_tokens")
+ print(" • Configurations are properly isolated in the database")
+ print(" • Updates to one server don't affect others")
+ print("="*60 + "\n")
+
+ # Cleanup
+ test_db.unlink()
+
+
+if __name__ == "__main__":
+ try:
+ test_per_server_config()
+ except AssertionError as e:
+ print(f"\n❌ Test Failed: {e}\n")
+ sys.exit(1)
+ except Exception as e:
+ print(f"\n❌ Unexpected Error: {e}\n")
+ import traceback
+ traceback.print_exc()
+ sys.exit(1)
From 206db55f6e8be3bfb31ca4da14a49fc33d11acfa Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 5 Nov 2025 04:56:54 +0000
Subject: [PATCH 4/6] Code review: Extract inline styles to CSS classes
- Add CSS classes for server instruction and selected server name
- Replace inline styles with proper CSS classes
- Use constants for test server IDs for better readability
- Improve code maintainability and consistency
Co-authored-by: franktheglock <169854515+franktheglock@users.noreply.github.com>
---
dashboard/templates/index.html | 14 ++++++++++++--
tests/test_server_config.py | 28 ++++++++++++++--------------
2 files changed, 26 insertions(+), 16 deletions(-)
diff --git a/dashboard/templates/index.html b/dashboard/templates/index.html
index 67d1b69..8976c71 100644
--- a/dashboard/templates/index.html
+++ b/dashboard/templates/index.html
@@ -456,6 +456,16 @@
font-size:13px;
color:var(--muted);
}
+ .server-instruction {
+ padding:0 0 12px 0;
+ font-size:13px;
+ color:var(--muted);
+ }
+ .selected-server-name {
+ font-size:13px;
+ color:var(--muted);
+ font-weight:400;
+ }
.toggle-switch {
display:flex;
align-items:center;
@@ -961,7 +971,7 @@
Tool Insights