This commit is contained in:
Slipstream 2025-05-03 17:57:01 -06:00
parent 5246a6151d
commit 1b825f3dd6
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD
2 changed files with 476 additions and 13 deletions

View File

@ -870,6 +870,205 @@ async def dashboard_get_user_guilds(current_user: dict = Depends(get_dashboard_u
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An internal error occurred while fetching guilds.")
# --- Dashboard Guild Settings Endpoints ---
@dashboard_api_app.get("/guilds/{guild_id}/channels", tags=["Dashboard Guild Settings"])
async def dashboard_get_guild_channels(
guild_id: int,
current_user: dict = Depends(get_dashboard_user),
_: bool = Depends(verify_dashboard_guild_admin) # Underscore indicates unused but required dependency
):
"""Fetches the channels for a specific guild for the dashboard."""
global http_session # Use the global aiohttp session
if not http_session:
raise HTTPException(status_code=500, detail="Internal server error: HTTP session not ready.")
log.info(f"Dashboard: Fetching channels for guild {guild_id} requested by user {current_user['user_id']}")
try:
# Use Discord Bot Token to fetch channels
bot_headers = {'Authorization': f'Bot {settings.DISCORD_BOT_TOKEN}'}
async with http_session.get(f"https://discord.com/api/v10/guilds/{guild_id}/channels", headers=bot_headers) as resp:
resp.raise_for_status()
channels = await resp.json()
# Filter and format channels
formatted_channels = []
for channel in channels:
formatted_channels.append({
"id": channel["id"],
"name": channel["name"],
"type": channel["type"],
"parent_id": channel.get("parent_id")
})
return formatted_channels
except aiohttp.ClientResponseError as e:
log.exception(f"Dashboard: HTTP error fetching guild channels: {e.status} {e.message}")
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail="Error communicating with Discord API.")
except Exception as e:
log.exception(f"Dashboard: Generic error fetching guild channels: {e}")
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An internal error occurred while fetching channels.")
@dashboard_api_app.get("/guilds/{guild_id}/roles", tags=["Dashboard Guild Settings"])
async def dashboard_get_guild_roles(
guild_id: int,
current_user: dict = Depends(get_dashboard_user),
_: bool = Depends(verify_dashboard_guild_admin) # Underscore indicates unused but required dependency
):
"""Fetches the roles for a specific guild for the dashboard."""
global http_session # Use the global aiohttp session
if not http_session:
raise HTTPException(status_code=500, detail="Internal server error: HTTP session not ready.")
log.info(f"Dashboard: Fetching roles for guild {guild_id} requested by user {current_user['user_id']}")
try:
# Use Discord Bot Token to fetch roles
bot_headers = {'Authorization': f'Bot {settings.DISCORD_BOT_TOKEN}'}
async with http_session.get(f"https://discord.com/api/v10/guilds/{guild_id}/roles", headers=bot_headers) as resp:
resp.raise_for_status()
roles = await resp.json()
# Filter and format roles
formatted_roles = []
for role in roles:
# Skip @everyone role
if role["name"] == "@everyone":
continue
formatted_roles.append({
"id": role["id"],
"name": role["name"],
"color": role["color"],
"position": role["position"],
"permissions": role["permissions"]
})
# Sort roles by position (highest first)
formatted_roles.sort(key=lambda r: r["position"], reverse=True)
return formatted_roles
except aiohttp.ClientResponseError as e:
log.exception(f"Dashboard: HTTP error fetching guild roles: {e.status} {e.message}")
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail="Error communicating with Discord API.")
except Exception as e:
log.exception(f"Dashboard: Generic error fetching guild roles: {e}")
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An internal error occurred while fetching roles.")
@dashboard_api_app.get("/guilds/{guild_id}/commands", tags=["Dashboard Guild Settings"])
async def dashboard_get_guild_commands(
guild_id: int,
current_user: dict = Depends(get_dashboard_user),
_: bool = Depends(verify_dashboard_guild_admin) # Underscore indicates unused but required dependency
):
"""Fetches the commands for a specific guild for the dashboard."""
global http_session # Use the global aiohttp session
if not http_session:
raise HTTPException(status_code=500, detail="Internal server error: HTTP session not ready.")
log.info(f"Dashboard: Fetching commands for guild {guild_id} requested by user {current_user['user_id']}")
try:
# Use Discord Bot Token to fetch application commands
bot_headers = {'Authorization': f'Bot {settings.DISCORD_BOT_TOKEN}'}
application_id = settings.DISCORD_CLIENT_ID # This should be the same as your bot's application ID
async with http_session.get(f"https://discord.com/api/v10/applications/{application_id}/guilds/{guild_id}/commands", headers=bot_headers) as resp:
resp.raise_for_status()
commands = await resp.json()
# Format commands
formatted_commands = []
for cmd in commands:
formatted_commands.append({
"id": cmd["id"],
"name": cmd["name"],
"description": cmd.get("description", ""),
"type": cmd.get("type", 1), # Default to CHAT_INPUT type
"options": cmd.get("options", [])
})
return formatted_commands
except aiohttp.ClientResponseError as e:
log.exception(f"Dashboard: HTTP error fetching guild commands: {e.status} {e.message}")
if e.status == 404:
# If no commands are registered yet, return an empty list
return []
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail="Error communicating with Discord API.")
except Exception as e:
log.exception(f"Dashboard: Generic error fetching guild commands: {e}")
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An internal error occurred while fetching commands.")
@dashboard_api_app.get("/settings", tags=["Dashboard Settings"])
async def dashboard_get_settings(current_user: dict = Depends(get_dashboard_user)):
"""Fetches the global AI settings for the dashboard."""
log.info(f"Dashboard: Fetching global settings requested by user {current_user['user_id']}")
try:
# Get settings from the database
settings_data = db.get_user_settings(current_user['user_id'])
if not settings_data:
# Return default settings if none exist
return {
"model": "openai/gpt-3.5-turbo",
"temperature": 0.7,
"max_tokens": 1000,
"system_message": "",
"character": "",
"character_info": "",
"custom_instructions": ""
}
return settings_data
except Exception as e:
log.exception(f"Dashboard: Error fetching global settings: {e}")
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An internal error occurred while fetching settings.")
@dashboard_api_app.post("/settings", tags=["Dashboard Settings"])
@dashboard_api_app.put("/settings", tags=["Dashboard Settings"])
async def dashboard_update_settings(request: Request, current_user: dict = Depends(get_dashboard_user)):
"""Updates the global AI settings for the dashboard."""
log.info(f"Dashboard: Updating global settings requested by user {current_user['user_id']}")
try:
# Parse the request body
body_text = await request.body()
body = json.loads(body_text.decode('utf-8'))
log.debug(f"Dashboard: Received settings update: {body}")
# Extract settings from the request body
settings_data = None
# Try different formats to be flexible
if "settings" in body:
settings_data = body["settings"]
elif isinstance(body, dict) and "model" in body:
# Direct settings object
settings_data = body
if not settings_data:
raise HTTPException(status_code=400, detail="Invalid settings format. Expected 'settings' field or direct settings object.")
# Create a UserSettings object
try:
settings = UserSettings.model_validate(settings_data)
except Exception as e:
log.exception(f"Dashboard: Error validating settings: {e}")
raise HTTPException(status_code=400, detail=f"Invalid settings data: {str(e)}")
# Save the settings
result = db.save_user_settings(current_user['user_id'], settings)
log.info(f"Dashboard: Successfully updated settings for user {current_user['user_id']}")
return result
except json.JSONDecodeError:
log.exception(f"Dashboard: Error decoding JSON in settings update")
raise HTTPException(status_code=400, detail="Invalid JSON in request body")
except Exception as e:
log.exception(f"Dashboard: Error updating settings: {e}")
raise HTTPException(status_code=500, detail=f"An internal error occurred while updating settings: {str(e)}")
@dashboard_api_app.get("/guilds/{guild_id}/settings", response_model=GuildSettingsResponse, tags=["Dashboard Guild Settings"])
async def dashboard_get_guild_settings(
guild_id: int,
@ -989,6 +1188,7 @@ async def dashboard_update_guild_settings(
# --- Dashboard Command Permission Endpoints ---
@dashboard_api_app.get("/guilds/{guild_id}/permissions", response_model=CommandPermissionsResponse, tags=["Dashboard Guild Settings"])
@dashboard_api_app.get("/guilds/{guild_id}/command-permissions", tags=["Dashboard Guild Settings"])
async def dashboard_get_all_guild_command_permissions(
guild_id: int,
current_user: dict = Depends(get_dashboard_user),
@ -1023,6 +1223,7 @@ async def dashboard_get_all_guild_command_permissions(
raise HTTPException(status_code=500, detail="Failed to fetch command permissions.")
@dashboard_api_app.post("/guilds/{guild_id}/permissions", status_code=status.HTTP_201_CREATED, tags=["Dashboard Guild Settings"])
@dashboard_api_app.post("/guilds/{guild_id}/command-permissions", status_code=status.HTTP_201_CREATED, tags=["Dashboard Guild Settings"])
async def dashboard_add_guild_command_permission(
guild_id: int,
permission: CommandPermission,
@ -1048,6 +1249,7 @@ async def dashboard_add_guild_command_permission(
raise HTTPException(status_code=500, detail="Failed to add command permission. Check server logs.")
@dashboard_api_app.delete("/guilds/{guild_id}/permissions", status_code=status.HTTP_200_OK, tags=["Dashboard Guild Settings"])
@dashboard_api_app.delete("/guilds/{guild_id}/command-permissions", status_code=status.HTTP_200_OK, tags=["Dashboard Guild Settings"])
async def dashboard_remove_guild_command_permission(
guild_id: int,
permission: CommandPermission,

View File

@ -50,6 +50,41 @@ function initSidebar() {
if (href && currentPath.includes(href)) {
item.classList.add('active');
}
// Add click event to nav items
item.addEventListener('click', (event) => {
// Prevent default only if it's a section link
if (href && href.startsWith('#')) {
event.preventDefault();
// Remove active class from all nav items
document.querySelectorAll('.nav-item').forEach(navItem => {
navItem.classList.remove('active');
});
// Add active class to clicked item
item.classList.add('active');
// Hide all sections
document.querySelectorAll('.dashboard-section').forEach(section => {
section.style.display = 'none';
});
// Show the target section
const sectionId = item.getAttribute('data-section');
if (sectionId) {
const section = document.getElementById(sectionId);
if (section) {
section.style.display = 'block';
}
}
// Close sidebar on mobile
if (window.innerWidth <= 768 && sidebar) {
sidebar.classList.remove('show');
}
}
});
});
}
@ -741,9 +776,23 @@ function loadGlobalSettings() {
const aiSettingsSection = document.getElementById('ai-settings-section');
if (!aiSettingsSection) return;
// Show loading state
const loadingContainer = document.createElement('div');
loadingContainer.className = 'loading-container';
loadingContainer.innerHTML = '<div class="loading-spinner"></div><p>Loading AI settings...</p>';
loadingContainer.style.textAlign = 'center';
loadingContainer.style.padding = '2rem';
aiSettingsSection.prepend(loadingContainer);
// Fetch global settings from API
API.get('/dashboard/api/settings')
.then(settings => {
// Remove loading container
loadingContainer.remove();
console.log('Loaded AI settings:', settings);
// Populate AI model select
const modelSelect = document.getElementById('ai-model-select');
if (modelSelect && settings.model) {
@ -753,9 +802,10 @@ function loadGlobalSettings() {
// Populate temperature slider
const temperatureSlider = document.getElementById('ai-temperature');
const temperatureValue = document.getElementById('temperature-value');
if (temperatureSlider && temperatureValue && settings.temperature) {
temperatureSlider.value = settings.temperature;
temperatureValue.textContent = settings.temperature;
if (temperatureSlider && temperatureValue) {
const temp = settings.temperature !== undefined ? settings.temperature : 0.7;
temperatureSlider.value = temp;
temperatureValue.textContent = temp;
// Add input event for live update
temperatureSlider.addEventListener('input', () => {
@ -765,36 +815,247 @@ function loadGlobalSettings() {
// Populate max tokens
const maxTokensInput = document.getElementById('ai-max-tokens');
if (maxTokensInput && settings.max_tokens) {
maxTokensInput.value = settings.max_tokens;
if (maxTokensInput) {
const maxTokens = settings.max_tokens !== undefined ? settings.max_tokens : 1000;
maxTokensInput.value = maxTokens;
}
// Populate character settings
const characterInput = document.getElementById('ai-character');
const characterInfoInput = document.getElementById('ai-character-info');
if (characterInput && settings.character) {
characterInput.value = settings.character;
if (characterInput) {
characterInput.value = settings.character || '';
}
if (characterInfoInput && settings.character_info) {
characterInfoInput.value = settings.character_info;
if (characterInfoInput) {
characterInfoInput.value = settings.character_info || '';
}
// Populate system prompt
const systemPromptInput = document.getElementById('ai-system-prompt');
if (systemPromptInput && settings.system_message) {
systemPromptInput.value = settings.system_message;
if (systemPromptInput) {
systemPromptInput.value = settings.system_message || '';
}
// Populate custom instructions
const customInstructionsInput = document.getElementById('ai-custom-instructions');
if (customInstructionsInput && settings.custom_instructions) {
customInstructionsInput.value = settings.custom_instructions;
if (customInstructionsInput) {
customInstructionsInput.value = settings.custom_instructions || '';
}
// Set up save buttons
setupAISettingsSaveButtons(settings);
})
.catch(error => {
console.error('Error loading global settings:', error);
loadingContainer.innerHTML = '<p class="text-danger">Error loading AI settings. Please try again.</p>';
Toast.error('Failed to load AI settings. Please try again.');
});
}
/**
* Set up AI settings save buttons
* @param {Object} initialSettings - The initial settings
*/
function setupAISettingsSaveButtons(initialSettings) {
// AI Settings save button
const saveAISettingsButton = document.getElementById('save-ai-settings-button');
if (saveAISettingsButton) {
saveAISettingsButton.addEventListener('click', () => {
const modelSelect = document.getElementById('ai-model-select');
const temperatureSlider = document.getElementById('ai-temperature');
const maxTokensInput = document.getElementById('ai-max-tokens');
const reasoningEnabled = document.getElementById('ai-reasoning-enabled');
const reasoningEffort = document.getElementById('ai-reasoning-effort');
const webSearchEnabled = document.getElementById('ai-web-search-enabled');
// Create settings object
const settings = {
...initialSettings, // Keep other settings
model: modelSelect ? modelSelect.value : initialSettings.model,
temperature: temperatureSlider ? parseFloat(temperatureSlider.value) : initialSettings.temperature,
max_tokens: maxTokensInput ? parseInt(maxTokensInput.value) : initialSettings.max_tokens
};
// Add optional settings if they exist
if (reasoningEnabled) {
settings.reasoning_enabled = reasoningEnabled.checked;
}
if (reasoningEffort) {
settings.reasoning_effort = reasoningEffort.value;
}
if (webSearchEnabled) {
settings.web_search_enabled = webSearchEnabled.checked;
}
// Save settings
saveSettings(settings, saveAISettingsButton, 'AI settings saved successfully');
});
}
// Character settings save button
const saveCharacterSettingsButton = document.getElementById('save-character-settings-button');
if (saveCharacterSettingsButton) {
saveCharacterSettingsButton.addEventListener('click', () => {
const characterInput = document.getElementById('ai-character');
const characterInfoInput = document.getElementById('ai-character-info');
const characterBreakdown = document.getElementById('ai-character-breakdown');
// Create settings object
const settings = {
...initialSettings, // Keep other settings
character: characterInput ? characterInput.value : initialSettings.character,
character_info: characterInfoInput ? characterInfoInput.value : initialSettings.character_info
};
// Add optional settings if they exist
if (characterBreakdown) {
settings.character_breakdown = characterBreakdown.checked;
}
// Save settings
saveSettings(settings, saveCharacterSettingsButton, 'Character settings saved successfully');
});
}
// System prompt save button
const saveSystemPromptButton = document.getElementById('save-system-prompt-button');
if (saveSystemPromptButton) {
saveSystemPromptButton.addEventListener('click', () => {
const systemPromptInput = document.getElementById('ai-system-prompt');
// Create settings object
const settings = {
...initialSettings, // Keep other settings
system_message: systemPromptInput ? systemPromptInput.value : initialSettings.system_message
};
// Save settings
saveSettings(settings, saveSystemPromptButton, 'System prompt saved successfully');
});
}
// Custom instructions save button
const saveCustomInstructionsButton = document.getElementById('save-custom-instructions-button');
if (saveCustomInstructionsButton) {
saveCustomInstructionsButton.addEventListener('click', () => {
const customInstructionsInput = document.getElementById('ai-custom-instructions');
// Create settings object
const settings = {
...initialSettings, // Keep other settings
custom_instructions: customInstructionsInput ? customInstructionsInput.value : initialSettings.custom_instructions
};
// Save settings
saveSettings(settings, saveCustomInstructionsButton, 'Custom instructions saved successfully');
});
}
// Clear buttons
const clearCharacterSettingsButton = document.getElementById('clear-character-settings-button');
if (clearCharacterSettingsButton) {
clearCharacterSettingsButton.addEventListener('click', () => {
const characterInput = document.getElementById('ai-character');
const characterInfoInput = document.getElementById('ai-character-info');
if (characterInput) characterInput.value = '';
if (characterInfoInput) characterInfoInput.value = '';
// Create settings object
const settings = {
...initialSettings, // Keep other settings
character: '',
character_info: ''
};
// Save settings
saveSettings(settings, clearCharacterSettingsButton, 'Character settings cleared');
});
}
const clearCustomInstructionsButton = document.getElementById('clear-custom-instructions-button');
if (clearCustomInstructionsButton) {
clearCustomInstructionsButton.addEventListener('click', () => {
const customInstructionsInput = document.getElementById('ai-custom-instructions');
if (customInstructionsInput) customInstructionsInput.value = '';
// Create settings object
const settings = {
...initialSettings, // Keep other settings
custom_instructions: ''
};
// Save settings
saveSettings(settings, clearCustomInstructionsButton, 'Custom instructions cleared');
});
}
// Reset buttons
const resetAISettingsButton = document.getElementById('reset-ai-settings-button');
if (resetAISettingsButton) {
resetAISettingsButton.addEventListener('click', () => {
const modelSelect = document.getElementById('ai-model-select');
const temperatureSlider = document.getElementById('ai-temperature');
const temperatureValue = document.getElementById('temperature-value');
const maxTokensInput = document.getElementById('ai-max-tokens');
if (modelSelect) modelSelect.value = 'openai/gpt-3.5-turbo';
if (temperatureSlider) temperatureSlider.value = 0.7;
if (temperatureValue) temperatureValue.textContent = 0.7;
if (maxTokensInput) maxTokensInput.value = 1000;
// Create settings object
const settings = {
...initialSettings, // Keep other settings
model: 'openai/gpt-3.5-turbo',
temperature: 0.7,
max_tokens: 1000
};
// Save settings
saveSettings(settings, resetAISettingsButton, 'AI settings reset to defaults');
});
}
const resetSystemPromptButton = document.getElementById('reset-system-prompt-button');
if (resetSystemPromptButton) {
resetSystemPromptButton.addEventListener('click', () => {
const systemPromptInput = document.getElementById('ai-system-prompt');
if (systemPromptInput) systemPromptInput.value = '';
// Create settings object
const settings = {
...initialSettings, // Keep other settings
system_message: ''
};
// Save settings
saveSettings(settings, resetSystemPromptButton, 'System prompt reset to default');
});
}
}
/**
* Save settings to the API
* @param {Object} settings - The settings to save
* @param {HTMLElement} button - The button that triggered the save
* @param {string} successMessage - The message to show on success
*/
function saveSettings(settings, button, successMessage) {
// Save settings to API
API.post('/dashboard/api/settings', { settings }, button)
.then(response => {
console.log('Settings saved:', response);
Toast.success(successMessage);
})
.catch(error => {
console.error('Error saving settings:', error);
Toast.error('Failed to save settings. Please try again.');
});
}