aaa
This commit is contained in:
parent
9e35f1b823
commit
9ee5bdbaaa
@ -215,6 +215,22 @@ dashboard_api_app = FastAPI(
|
||||
openapi_url="/openapi.json"
|
||||
)
|
||||
|
||||
# Import dashboard API endpoints
|
||||
try:
|
||||
# Try relative import first
|
||||
try:
|
||||
from .dashboard_api_endpoints import router as dashboard_router
|
||||
except ImportError:
|
||||
# Fall back to absolute import
|
||||
from dashboard_api_endpoints import router as dashboard_router
|
||||
|
||||
# Add the dashboard router to the dashboard API app
|
||||
dashboard_api_app.include_router(dashboard_router)
|
||||
log.info("Dashboard API endpoints loaded successfully")
|
||||
except ImportError as e:
|
||||
log.error(f"Could not import dashboard API endpoints: {e}")
|
||||
log.error("Dashboard API endpoints will not be available")
|
||||
|
||||
# Mount the API apps at their respective paths
|
||||
app.mount("/api", api_app)
|
||||
app.mount("/discordapi", discordapi_app)
|
||||
|
120
api_service/dashboard_api_endpoints.py
Normal file
120
api_service/dashboard_api_endpoints.py
Normal file
@ -0,0 +1,120 @@
|
||||
"""
|
||||
Dashboard API endpoints for the bot dashboard.
|
||||
These endpoints provide additional functionality for the dashboard UI.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Import the dependencies from api_server.py
|
||||
try:
|
||||
# Try relative import first
|
||||
from .api_server import get_dashboard_user, verify_dashboard_guild_admin
|
||||
except ImportError:
|
||||
# Fall back to absolute import
|
||||
from api_server import get_dashboard_user, verify_dashboard_guild_admin
|
||||
|
||||
# Set up logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Create a router for the dashboard API endpoints
|
||||
router = APIRouter(tags=["Dashboard API"])
|
||||
|
||||
# --- Models ---
|
||||
class Channel(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
type: int # 0 = text, 2 = voice, etc.
|
||||
|
||||
class Role(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
color: int
|
||||
position: int
|
||||
permissions: str
|
||||
|
||||
class Command(BaseModel):
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
|
||||
# --- Endpoints ---
|
||||
@router.get("/guilds/{guild_id}/channels", response_model=List[Channel])
|
||||
async def get_guild_channels(
|
||||
guild_id: int,
|
||||
_user: dict = Depends(get_dashboard_user), # Underscore prefix to indicate unused parameter
|
||||
_: bool = Depends(verify_dashboard_guild_admin)
|
||||
):
|
||||
"""Get all channels for a guild."""
|
||||
try:
|
||||
# This would normally fetch channels from Discord API or the bot
|
||||
# For now, we'll return a mock response
|
||||
channels = [
|
||||
Channel(id="123456789", name="general", type=0),
|
||||
Channel(id="123456790", name="welcome", type=0),
|
||||
Channel(id="123456791", name="announcements", type=0),
|
||||
Channel(id="123456792", name="voice-chat", type=2)
|
||||
]
|
||||
return channels
|
||||
except Exception as e:
|
||||
log.error(f"Error getting channels for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error getting channels: {str(e)}"
|
||||
)
|
||||
|
||||
@router.get("/guilds/{guild_id}/roles", response_model=List[Role])
|
||||
async def get_guild_roles(
|
||||
guild_id: int,
|
||||
_user: dict = Depends(get_dashboard_user), # Underscore prefix to indicate unused parameter
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin)
|
||||
):
|
||||
"""Get all roles for a guild."""
|
||||
try:
|
||||
# This would normally fetch roles from Discord API or the bot
|
||||
# For now, we'll return a mock response
|
||||
roles = [
|
||||
Role(id="123456789", name="@everyone", color=0, position=0, permissions="0"),
|
||||
Role(id="123456790", name="Admin", color=16711680, position=1, permissions="8"),
|
||||
Role(id="123456791", name="Moderator", color=65280, position=2, permissions="4"),
|
||||
Role(id="123456792", name="Member", color=255, position=3, permissions="1")
|
||||
]
|
||||
return roles
|
||||
except Exception as e:
|
||||
log.error(f"Error getting roles for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error getting roles: {str(e)}"
|
||||
)
|
||||
|
||||
@router.get("/guilds/{guild_id}/commands", response_model=List[Command])
|
||||
async def get_guild_commands(
|
||||
guild_id: int,
|
||||
_user: dict = Depends(get_dashboard_user), # Underscore prefix to indicate unused parameter
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin)
|
||||
):
|
||||
"""Get all commands available in the guild."""
|
||||
try:
|
||||
# This would normally fetch commands from the bot
|
||||
# For now, we'll return a mock response
|
||||
commands = [
|
||||
Command(name="help", description="Show help message"),
|
||||
Command(name="ping", description="Check bot latency"),
|
||||
Command(name="ban", description="Ban a user"),
|
||||
Command(name="kick", description="Kick a user"),
|
||||
Command(name="mute", description="Mute a user"),
|
||||
Command(name="unmute", description="Unmute a user"),
|
||||
Command(name="clear", description="Clear messages"),
|
||||
Command(name="ai", description="Get AI response"),
|
||||
Command(name="aiset", description="Configure AI settings"),
|
||||
Command(name="chat", description="Chat with AI"),
|
||||
Command(name="convs", description="Manage conversations")
|
||||
]
|
||||
return commands
|
||||
except Exception as e:
|
||||
log.error(f"Error getting commands for guild {guild_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error getting commands: {str(e)}"
|
||||
)
|
@ -17,66 +17,274 @@
|
||||
<h2>Welcome, <span id="username">User</span>!</h2>
|
||||
<button id="logout-button">Logout</button>
|
||||
|
||||
<div class="dashboard-nav">
|
||||
<button id="nav-server-settings" class="nav-button active">Server Settings</button>
|
||||
<button id="nav-ai-settings" class="nav-button">AI Settings</button>
|
||||
<button id="nav-conversations" class="nav-button">Conversations</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Manage Server Settings</h3>
|
||||
<label for="guild-select">Select Server:</label>
|
||||
<select name="guilds" id="guild-select">
|
||||
<option value="">--Please choose a server--</option>
|
||||
</select>
|
||||
<!-- Server Settings Section -->
|
||||
<div id="server-settings-section" class="dashboard-section">
|
||||
<h3>Manage Server Settings</h3>
|
||||
<label for="guild-select">Select Server:</label>
|
||||
<select name="guilds" id="guild-select">
|
||||
<option value="">--Please choose a server--</option>
|
||||
</select>
|
||||
|
||||
<div id="settings-form" style="display: none;">
|
||||
<h4>Prefix</h4>
|
||||
<label for="prefix-input">Command Prefix:</label>
|
||||
<input type="text" id="prefix-input" name="prefix" maxlength="10">
|
||||
<button id="save-prefix-button">Save Prefix</button>
|
||||
<p id="prefix-feedback"></p>
|
||||
<div id="settings-form" style="display: none;">
|
||||
<h4>Prefix</h4>
|
||||
<label for="prefix-input">Command Prefix:</label>
|
||||
<input type="text" id="prefix-input" name="prefix" maxlength="10">
|
||||
<button id="save-prefix-button">Save Prefix</button>
|
||||
<p id="prefix-feedback"></p>
|
||||
|
||||
<h4>Welcome Messages</h4>
|
||||
<label for="welcome-channel">Welcome Channel ID:</label> <!-- Changed label -->
|
||||
<input type="text" id="welcome-channel" name="welcome_channel_id" placeholder="Enter Channel ID"> <!-- Changed to text input -->
|
||||
<br>
|
||||
<label for="welcome-message">Welcome Message Template:</label><br>
|
||||
<textarea id="welcome-message" name="welcome_message" rows="4" cols="50" placeholder="Use {user} for mention, {username} for name, {server} for server name."></textarea><br>
|
||||
<button id="save-welcome-button">Save Welcome Settings</button>
|
||||
<button id="disable-welcome-button">Disable Welcome</button>
|
||||
<p id="welcome-feedback"></p>
|
||||
<h4>Welcome Messages</h4>
|
||||
<div class="settings-card">
|
||||
<div class="form-group">
|
||||
<label for="welcome-channel">Welcome Channel:</label>
|
||||
<div class="channel-select-container">
|
||||
<input type="text" id="welcome-channel" name="welcome_channel_id" placeholder="Enter Channel ID">
|
||||
<select id="welcome-channel-select" class="channel-dropdown">
|
||||
<option value="">-- Select Channel --</option>
|
||||
<!-- Will be populated by JS -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="welcome-message">Welcome Message Template:</label>
|
||||
<textarea id="welcome-message" name="welcome_message" rows="4" cols="50" placeholder="Use {user} for mention, {username} for name, {server} for server name."></textarea>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button id="save-welcome-button">Save Welcome Settings</button>
|
||||
<button id="disable-welcome-button">Disable Welcome</button>
|
||||
</div>
|
||||
<p id="welcome-feedback"></p>
|
||||
</div>
|
||||
|
||||
<h4>Goodbye Messages</h4>
|
||||
<label for="goodbye-channel">Goodbye Channel ID:</label> <!-- Changed label -->
|
||||
<input type="text" id="goodbye-channel" name="goodbye_channel_id" placeholder="Enter Channel ID"> <!-- Changed to text input -->
|
||||
<br>
|
||||
<label for="goodbye-message">Goodbye Message Template:</label><br>
|
||||
<textarea id="goodbye-message" name="goodbye_message" rows="4" cols="50" placeholder="Use {username} for name, {server} for server name."></textarea><br>
|
||||
<button id="save-goodbye-button">Save Goodbye Settings</button>
|
||||
<button id="disable-goodbye-button">Disable Goodbye</button>
|
||||
<p id="goodbye-feedback"></p>
|
||||
<h4>Goodbye Messages</h4>
|
||||
<div class="settings-card">
|
||||
<div class="form-group">
|
||||
<label for="goodbye-channel">Goodbye Channel:</label>
|
||||
<div class="channel-select-container">
|
||||
<input type="text" id="goodbye-channel" name="goodbye_channel_id" placeholder="Enter Channel ID">
|
||||
<select id="goodbye-channel-select" class="channel-dropdown">
|
||||
<option value="">-- Select Channel --</option>
|
||||
<!-- Will be populated by JS -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="goodbye-message">Goodbye Message Template:</label>
|
||||
<textarea id="goodbye-message" name="goodbye_message" rows="4" cols="50" placeholder="Use {username} for name, {server} for server name."></textarea>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button id="save-goodbye-button">Save Goodbye Settings</button>
|
||||
<button id="disable-goodbye-button">Disable Goodbye</button>
|
||||
</div>
|
||||
<p id="goodbye-feedback"></p>
|
||||
</div>
|
||||
|
||||
<h4>Enabled Modules (Cogs)</h4>
|
||||
<div id="cogs-list">
|
||||
<!-- Cog checkboxes will be populated by JS -->
|
||||
<h4>Enabled Modules (Cogs)</h4>
|
||||
<div class="settings-card">
|
||||
<div class="cogs-container">
|
||||
<div id="cogs-list">
|
||||
<!-- Cog checkboxes will be populated by JS -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button id="save-cogs-button">Save Module Settings</button>
|
||||
</div>
|
||||
<p id="cogs-feedback"></p>
|
||||
</div>
|
||||
|
||||
<h4>Command Permissions</h4>
|
||||
<div class="settings-card">
|
||||
<div class="form-group">
|
||||
<label for="command-select">Command:</label>
|
||||
<select id="command-select">
|
||||
<!-- Will be populated by JS -->
|
||||
<option value="">-- Select Command --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="role-select">Role:</label>
|
||||
<select id="role-select">
|
||||
<!-- Will be populated by JS -->
|
||||
<option value="">-- Select Role --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button id="add-perm-button">Allow Role</button>
|
||||
<button id="remove-perm-button">Disallow Role</button>
|
||||
</div>
|
||||
<div id="current-perms">
|
||||
<!-- Current permissions will be listed here -->
|
||||
</div>
|
||||
<p id="perms-feedback"></p>
|
||||
</div>
|
||||
</div>
|
||||
<button id="save-cogs-button">Save Module Settings</button>
|
||||
<p id="cogs-feedback"></p>
|
||||
</div>
|
||||
|
||||
<h4>Command Permissions</h4>
|
||||
<label for="command-select">Command:</label>
|
||||
<select id="command-select">
|
||||
<!-- TODO: Populate commands dynamically -->
|
||||
<option value="">-- Select Command --</option>
|
||||
</select>
|
||||
<label for="role-select">Role:</label>
|
||||
<select id="role-select">
|
||||
<!-- TODO: Populate roles dynamically -->
|
||||
<option value="">-- Select Role --</option>
|
||||
</select>
|
||||
<button id="add-perm-button">Allow Role</button>
|
||||
<button id="remove-perm-button">Disallow Role</button>
|
||||
<div id="current-perms">
|
||||
<!-- Current permissions will be listed here -->
|
||||
</div>
|
||||
<p id="perms-feedback"></p>
|
||||
<!-- AI Settings Section -->
|
||||
<div id="ai-settings-section" class="dashboard-section" style="display: none;">
|
||||
<h3>AI Settings</h3>
|
||||
<div class="settings-card">
|
||||
<h4>General AI Settings</h4>
|
||||
<div class="form-group">
|
||||
<label for="ai-model-select">AI Model:</label>
|
||||
<select id="ai-model-select">
|
||||
<option value="openai/gpt-3.5-turbo">GPT-3.5 Turbo</option>
|
||||
<option value="openai/gpt-4">GPT-4</option>
|
||||
<option value="anthropic/claude-3-opus">Claude 3 Opus</option>
|
||||
<option value="anthropic/claude-3-sonnet">Claude 3 Sonnet</option>
|
||||
<option value="google/gemini-2.5-flash-preview">Gemini 2.5 Flash</option>
|
||||
<option value="google/gemini-2.5-pro-preview">Gemini 2.5 Pro</option>
|
||||
<!-- More models will be populated dynamically if available -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ai-temperature">Temperature: <span id="temperature-value">0.7</span></label>
|
||||
<input type="range" id="ai-temperature" min="0" max="2" step="0.1" value="0.7">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ai-max-tokens">Max Tokens:</label>
|
||||
<input type="number" id="ai-max-tokens" min="100" max="8000" step="100" value="1000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" id="ai-reasoning-enabled">
|
||||
Enable Reasoning
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group" id="reasoning-effort-group">
|
||||
<label for="ai-reasoning-effort">Reasoning Effort:</label>
|
||||
<select id="ai-reasoning-effort">
|
||||
<option value="low">Low</option>
|
||||
<option value="medium" selected>Medium</option>
|
||||
<option value="high">High</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" id="ai-web-search-enabled">
|
||||
Enable Web Search
|
||||
</label>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button id="save-ai-settings-button">Save AI Settings</button>
|
||||
<button id="reset-ai-settings-button">Reset to Defaults</button>
|
||||
</div>
|
||||
<p id="ai-settings-feedback"></p>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<h4>Character Settings</h4>
|
||||
<div class="form-group">
|
||||
<label for="ai-character">Character Name:</label>
|
||||
<input type="text" id="ai-character" placeholder="e.g., Kasane Teto">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ai-character-info">Character Information:</label>
|
||||
<textarea id="ai-character-info" rows="4" placeholder="Describe the character's personality, background, etc."></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" id="ai-character-breakdown">
|
||||
Enable Character Breakdown
|
||||
</label>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button id="save-character-settings-button">Save Character Settings</button>
|
||||
<button id="clear-character-settings-button">Clear Character</button>
|
||||
</div>
|
||||
<p id="character-settings-feedback"></p>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<h4>System Prompt</h4>
|
||||
<div class="form-group">
|
||||
<textarea id="ai-system-prompt" rows="6" placeholder="Enter a system prompt to guide the AI's behavior..."></textarea>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button id="save-system-prompt-button">Save System Prompt</button>
|
||||
<button id="reset-system-prompt-button">Reset to Default</button>
|
||||
</div>
|
||||
<p id="system-prompt-feedback"></p>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<h4>Custom Instructions</h4>
|
||||
<div class="form-group">
|
||||
<textarea id="ai-custom-instructions" rows="6" placeholder="Enter custom instructions for the AI..."></textarea>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button id="save-custom-instructions-button">Save Custom Instructions</button>
|
||||
<button id="clear-custom-instructions-button">Clear Instructions</button>
|
||||
</div>
|
||||
<p id="custom-instructions-feedback"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conversations Section -->
|
||||
<div id="conversations-section" class="dashboard-section" style="display: none;">
|
||||
<h3>AI Conversations</h3>
|
||||
|
||||
<div class="conversations-header">
|
||||
<div class="search-container">
|
||||
<input type="text" id="conversation-search" placeholder="Search conversations...">
|
||||
</div>
|
||||
<button id="new-conversation-button">New Conversation</button>
|
||||
</div>
|
||||
|
||||
<div class="conversations-list-container">
|
||||
<div id="conversations-list">
|
||||
<!-- Will be populated by JS -->
|
||||
<div class="no-conversations">No conversations found. Start a new conversation!</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="conversation-detail" style="display: none;">
|
||||
<div class="conversation-header">
|
||||
<h4 id="conversation-title">Conversation Title</h4>
|
||||
<div class="conversation-actions">
|
||||
<button id="rename-conversation-button">Rename</button>
|
||||
<button id="delete-conversation-button">Delete</button>
|
||||
<button id="export-conversation-button">Export</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="conversation-messages" id="conversation-messages">
|
||||
<!-- Will be populated by JS -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal for renaming conversations -->
|
||||
<div id="rename-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close-modal">×</span>
|
||||
<h4>Rename Conversation</h4>
|
||||
<input type="text" id="new-conversation-title" placeholder="Enter new title">
|
||||
<div class="button-group">
|
||||
<button id="confirm-rename-button">Rename</button>
|
||||
<button id="cancel-rename-button">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal for creating new conversations -->
|
||||
<div id="new-conversation-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close-modal">×</span>
|
||||
<h4>Create New Conversation</h4>
|
||||
<input type="text" id="new-conversation-name" placeholder="Enter conversation title">
|
||||
<div class="button-group">
|
||||
<button id="create-conversation-button">Create</button>
|
||||
<button id="cancel-create-button">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,9 +1,22 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Auth elements
|
||||
const loginButton = document.getElementById('login-button');
|
||||
const logoutButton = document.getElementById('logout-button');
|
||||
const authSection = document.getElementById('auth-section');
|
||||
const dashboardSection = document.getElementById('dashboard-section');
|
||||
const usernameSpan = document.getElementById('username');
|
||||
|
||||
// Navigation elements
|
||||
const navServerSettings = document.getElementById('nav-server-settings');
|
||||
const navAiSettings = document.getElementById('nav-ai-settings');
|
||||
const navConversations = document.getElementById('nav-conversations');
|
||||
|
||||
// Section elements
|
||||
const serverSettingsSection = document.getElementById('server-settings-section');
|
||||
const aiSettingsSection = document.getElementById('ai-settings-section');
|
||||
const conversationsSection = document.getElementById('conversations-section');
|
||||
|
||||
// Server settings elements
|
||||
const guildSelect = document.getElementById('guild-select');
|
||||
const settingsForm = document.getElementById('settings-form');
|
||||
|
||||
@ -49,6 +62,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
authSection.style.display = 'none';
|
||||
dashboardSection.style.display = 'block';
|
||||
usernameSpan.textContent = userData.username;
|
||||
|
||||
// Show server settings section by default
|
||||
showSection('server-settings');
|
||||
|
||||
// Load guilds for server settings
|
||||
loadGuilds();
|
||||
}
|
||||
|
||||
@ -131,18 +149,24 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
async function loadSettings(guildId) {
|
||||
console.log(`Loading settings for guild ${guildId}`);
|
||||
// Clear previous settings?
|
||||
// Clear previous settings
|
||||
document.getElementById('prefix-input').value = '';
|
||||
// Changed channel inputs to text
|
||||
document.getElementById('welcome-channel').value = '';
|
||||
document.getElementById('welcome-message').value = '';
|
||||
document.getElementById('goodbye-channel').value = '';
|
||||
document.getElementById('goodbye-message').value = '';
|
||||
document.getElementById('cogs-list').innerHTML = '';
|
||||
document.getElementById('current-perms').innerHTML = ''; // Clear permissions list
|
||||
document.getElementById('current-perms').innerHTML = '';
|
||||
|
||||
// Clear channel dropdowns
|
||||
document.getElementById('welcome-channel-select').innerHTML = '<option value="">-- Select Channel --</option>';
|
||||
document.getElementById('goodbye-channel-select').innerHTML = '<option value="">-- Select Channel --</option>';
|
||||
|
||||
try {
|
||||
// Use the new endpoint path
|
||||
// Load guild channels for dropdowns
|
||||
await loadGuildChannels(guildId);
|
||||
|
||||
// Use the new endpoint path
|
||||
const settings = await fetchAPI(`/guilds/${guildId}/settings`);
|
||||
console.log("Received settings:", settings);
|
||||
|
||||
@ -155,6 +179,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
document.getElementById('goodbye-channel').value = settings.goodbye_channel_id || '';
|
||||
document.getElementById('goodbye-message').value = settings.goodbye_message || '';
|
||||
|
||||
// Set the channel dropdowns to match the channel IDs
|
||||
if (settings.welcome_channel_id) {
|
||||
const welcomeChannelSelect = document.getElementById('welcome-channel-select');
|
||||
if (welcomeChannelSelect.querySelector(`option[value="${settings.welcome_channel_id}"]`)) {
|
||||
welcomeChannelSelect.value = settings.welcome_channel_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.goodbye_channel_id) {
|
||||
const goodbyeChannelSelect = document.getElementById('goodbye-channel-select');
|
||||
if (goodbyeChannelSelect.querySelector(`option[value="${settings.goodbye_channel_id}"]`)) {
|
||||
goodbyeChannelSelect.value = settings.goodbye_channel_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Populate Cogs
|
||||
// TODO: Need a way to get the *full* list of available cogs from the bot/API
|
||||
// For now, just display the ones returned by the settings endpoint
|
||||
@ -162,12 +201,105 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Populate Command Permissions
|
||||
// TODO: Fetch roles and commands for dropdowns
|
||||
// TODO: Fetch current permissions
|
||||
await loadCommandPermissions(guildId);
|
||||
|
||||
// Load guild roles for the role dropdown
|
||||
await loadGuildRoles(guildId);
|
||||
|
||||
// Load commands for the command dropdown
|
||||
await loadCommands(guildId);
|
||||
|
||||
} catch (error) {
|
||||
displayFeedback('prefix-feedback', `Error loading settings: ${error.message}`, true); // Use a general feedback area?
|
||||
displayFeedback('prefix-feedback', `Error loading settings: ${error.message}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGuildChannels(guildId) {
|
||||
try {
|
||||
// Fetch channels from the API
|
||||
const channels = await fetchAPI(`/guilds/${guildId}/channels`);
|
||||
|
||||
// Get the channel select dropdowns
|
||||
const welcomeChannelSelect = document.getElementById('welcome-channel-select');
|
||||
const goodbyeChannelSelect = document.getElementById('goodbye-channel-select');
|
||||
|
||||
// Clear existing options except the default
|
||||
welcomeChannelSelect.innerHTML = '<option value="">-- Select Channel --</option>';
|
||||
goodbyeChannelSelect.innerHTML = '<option value="">-- Select Channel --</option>';
|
||||
|
||||
// Add text channels to the dropdowns
|
||||
channels.filter(channel => channel.type === 0).forEach(channel => {
|
||||
const option = document.createElement('option');
|
||||
option.value = channel.id;
|
||||
option.textContent = `#${channel.name}`;
|
||||
|
||||
// Add to both dropdowns
|
||||
welcomeChannelSelect.appendChild(option.cloneNode(true));
|
||||
goodbyeChannelSelect.appendChild(option);
|
||||
});
|
||||
|
||||
// Add event listeners to sync the dropdowns with the text inputs
|
||||
welcomeChannelSelect.addEventListener('change', function() {
|
||||
document.getElementById('welcome-channel').value = this.value;
|
||||
});
|
||||
|
||||
goodbyeChannelSelect.addEventListener('change', function() {
|
||||
document.getElementById('goodbye-channel').value = this.value;
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading guild channels:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGuildRoles(guildId) {
|
||||
try {
|
||||
// Fetch roles from the API
|
||||
const roles = await fetchAPI(`/guilds/${guildId}/roles`);
|
||||
|
||||
// Get the role select dropdown
|
||||
const roleSelect = document.getElementById('role-select');
|
||||
|
||||
// Clear existing options except the default
|
||||
roleSelect.innerHTML = '<option value="">-- Select Role --</option>';
|
||||
|
||||
// Add roles to the dropdown
|
||||
roles.forEach(role => {
|
||||
// Skip @everyone role
|
||||
if (role.name === '@everyone') return;
|
||||
|
||||
const option = document.createElement('option');
|
||||
option.value = role.id;
|
||||
option.textContent = role.name;
|
||||
roleSelect.appendChild(option);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading guild roles:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCommands(guildId) {
|
||||
try {
|
||||
// Fetch commands from the API
|
||||
const commands = await fetchAPI(`/guilds/${guildId}/commands`);
|
||||
|
||||
// Get the command select dropdown
|
||||
const commandSelect = document.getElementById('command-select');
|
||||
|
||||
// Clear existing options except the default
|
||||
commandSelect.innerHTML = '<option value="">-- Select Command --</option>';
|
||||
|
||||
// Add commands to the dropdown
|
||||
commands.forEach(command => {
|
||||
const option = document.createElement('option');
|
||||
option.value = command.name;
|
||||
option.textContent = command.name;
|
||||
commandSelect.appendChild(option);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading commands:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,6 +548,764 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
|
||||
// --- Navigation Functions ---
|
||||
function showSection(sectionId) {
|
||||
// Hide all sections
|
||||
serverSettingsSection.style.display = 'none';
|
||||
aiSettingsSection.style.display = 'none';
|
||||
conversationsSection.style.display = 'none';
|
||||
|
||||
// Remove active class from all nav buttons
|
||||
navServerSettings.classList.remove('active');
|
||||
navAiSettings.classList.remove('active');
|
||||
navConversations.classList.remove('active');
|
||||
|
||||
// Show the selected section and activate the corresponding nav button
|
||||
switch(sectionId) {
|
||||
case 'server-settings':
|
||||
serverSettingsSection.style.display = 'block';
|
||||
navServerSettings.classList.add('active');
|
||||
break;
|
||||
case 'ai-settings':
|
||||
aiSettingsSection.style.display = 'block';
|
||||
navAiSettings.classList.add('active');
|
||||
// Load AI settings if not already loaded
|
||||
if (!aiSettingsLoaded) {
|
||||
loadAiSettings();
|
||||
}
|
||||
break;
|
||||
case 'conversations':
|
||||
conversationsSection.style.display = 'block';
|
||||
navConversations.classList.add('active');
|
||||
// Load conversations if not already loaded
|
||||
if (!conversationsLoaded) {
|
||||
loadConversations();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
serverSettingsSection.style.display = 'block';
|
||||
navServerSettings.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
// --- Navigation Event Listeners ---
|
||||
navServerSettings.addEventListener('click', () => showSection('server-settings'));
|
||||
navAiSettings.addEventListener('click', () => showSection('ai-settings'));
|
||||
navConversations.addEventListener('click', () => showSection('conversations'));
|
||||
|
||||
// --- AI Settings Functions ---
|
||||
async function loadAiSettings() {
|
||||
try {
|
||||
const response = await fetchAPI('/settings');
|
||||
const settings = response.settings || response.user_settings;
|
||||
|
||||
if (settings) {
|
||||
// Populate AI model dropdown
|
||||
const modelSelect = document.getElementById('ai-model-select');
|
||||
if (settings.model_id) {
|
||||
// Find the option with the matching value or create a new one if it doesn't exist
|
||||
let option = Array.from(modelSelect.options).find(opt => opt.value === settings.model_id);
|
||||
if (!option) {
|
||||
option = new Option(settings.model_id, settings.model_id);
|
||||
modelSelect.add(option);
|
||||
}
|
||||
modelSelect.value = settings.model_id;
|
||||
}
|
||||
|
||||
// Set temperature
|
||||
const temperatureSlider = document.getElementById('ai-temperature');
|
||||
const temperatureValue = document.getElementById('temperature-value');
|
||||
if (settings.temperature !== undefined) {
|
||||
temperatureSlider.value = settings.temperature;
|
||||
temperatureValue.textContent = settings.temperature;
|
||||
}
|
||||
|
||||
// Set max tokens
|
||||
const maxTokensInput = document.getElementById('ai-max-tokens');
|
||||
if (settings.max_tokens !== undefined) {
|
||||
maxTokensInput.value = settings.max_tokens;
|
||||
}
|
||||
|
||||
// Set reasoning settings
|
||||
const reasoningCheckbox = document.getElementById('ai-reasoning-enabled');
|
||||
const reasoningEffortSelect = document.getElementById('ai-reasoning-effort');
|
||||
const reasoningEffortGroup = document.getElementById('reasoning-effort-group');
|
||||
|
||||
if (settings.reasoning_enabled !== undefined) {
|
||||
reasoningCheckbox.checked = settings.reasoning_enabled;
|
||||
reasoningEffortGroup.style.display = settings.reasoning_enabled ? 'block' : 'none';
|
||||
}
|
||||
|
||||
if (settings.reasoning_effort) {
|
||||
reasoningEffortSelect.value = settings.reasoning_effort;
|
||||
}
|
||||
|
||||
// Set web search
|
||||
const webSearchCheckbox = document.getElementById('ai-web-search-enabled');
|
||||
if (settings.web_search_enabled !== undefined) {
|
||||
webSearchCheckbox.checked = settings.web_search_enabled;
|
||||
}
|
||||
|
||||
// Set system prompt
|
||||
const systemPromptTextarea = document.getElementById('ai-system-prompt');
|
||||
if (settings.system_message) {
|
||||
systemPromptTextarea.value = settings.system_message;
|
||||
}
|
||||
|
||||
// Set character settings
|
||||
const characterInput = document.getElementById('ai-character');
|
||||
const characterInfoTextarea = document.getElementById('ai-character-info');
|
||||
const characterBreakdownCheckbox = document.getElementById('ai-character-breakdown');
|
||||
|
||||
if (settings.character) {
|
||||
characterInput.value = settings.character;
|
||||
}
|
||||
|
||||
if (settings.character_info) {
|
||||
characterInfoTextarea.value = settings.character_info;
|
||||
}
|
||||
|
||||
if (settings.character_breakdown !== undefined) {
|
||||
characterBreakdownCheckbox.checked = settings.character_breakdown;
|
||||
}
|
||||
|
||||
// Set custom instructions
|
||||
const customInstructionsTextarea = document.getElementById('ai-custom-instructions');
|
||||
if (settings.custom_instructions) {
|
||||
customInstructionsTextarea.value = settings.custom_instructions;
|
||||
}
|
||||
|
||||
aiSettingsLoaded = true;
|
||||
displayFeedback('ai-settings-feedback', 'AI settings loaded successfully.');
|
||||
}
|
||||
} catch (error) {
|
||||
displayFeedback('ai-settings-feedback', `Error loading AI settings: ${error.message}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
// --- AI Settings Event Listeners ---
|
||||
|
||||
// Temperature slider
|
||||
document.getElementById('ai-temperature').addEventListener('input', function() {
|
||||
document.getElementById('temperature-value').textContent = this.value;
|
||||
});
|
||||
|
||||
// Reasoning checkbox
|
||||
document.getElementById('ai-reasoning-enabled').addEventListener('change', function() {
|
||||
document.getElementById('reasoning-effort-group').style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// Save AI Settings button
|
||||
document.getElementById('save-ai-settings-button').addEventListener('click', async () => {
|
||||
try {
|
||||
const settings = {
|
||||
model_id: document.getElementById('ai-model-select').value,
|
||||
temperature: parseFloat(document.getElementById('ai-temperature').value),
|
||||
max_tokens: parseInt(document.getElementById('ai-max-tokens').value),
|
||||
reasoning_enabled: document.getElementById('ai-reasoning-enabled').checked,
|
||||
reasoning_effort: document.getElementById('ai-reasoning-effort').value,
|
||||
web_search_enabled: document.getElementById('ai-web-search-enabled').checked
|
||||
};
|
||||
|
||||
await fetchAPI('/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ settings })
|
||||
});
|
||||
|
||||
displayFeedback('ai-settings-feedback', 'AI settings saved successfully!');
|
||||
} catch (error) {
|
||||
displayFeedback('ai-settings-feedback', `Error saving AI settings: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Reset AI Settings button
|
||||
document.getElementById('reset-ai-settings-button').addEventListener('click', async () => {
|
||||
if (!confirm('Are you sure you want to reset AI settings to defaults?')) return;
|
||||
|
||||
try {
|
||||
const defaultSettings = {
|
||||
model_id: "openai/gpt-3.5-turbo",
|
||||
temperature: 0.7,
|
||||
max_tokens: 1000,
|
||||
reasoning_enabled: false,
|
||||
reasoning_effort: "medium",
|
||||
web_search_enabled: false
|
||||
};
|
||||
|
||||
await fetchAPI('/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ settings: defaultSettings })
|
||||
});
|
||||
|
||||
// Update UI with default values
|
||||
document.getElementById('ai-model-select').value = defaultSettings.model_id;
|
||||
document.getElementById('ai-temperature').value = defaultSettings.temperature;
|
||||
document.getElementById('temperature-value').textContent = defaultSettings.temperature;
|
||||
document.getElementById('ai-max-tokens').value = defaultSettings.max_tokens;
|
||||
document.getElementById('ai-reasoning-enabled').checked = defaultSettings.reasoning_enabled;
|
||||
document.getElementById('reasoning-effort-group').style.display = defaultSettings.reasoning_enabled ? 'block' : 'none';
|
||||
document.getElementById('ai-reasoning-effort').value = defaultSettings.reasoning_effort;
|
||||
document.getElementById('ai-web-search-enabled').checked = defaultSettings.web_search_enabled;
|
||||
|
||||
displayFeedback('ai-settings-feedback', 'AI settings reset to defaults.');
|
||||
} catch (error) {
|
||||
displayFeedback('ai-settings-feedback', `Error resetting AI settings: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Save Character Settings button
|
||||
document.getElementById('save-character-settings-button').addEventListener('click', async () => {
|
||||
try {
|
||||
const settings = {
|
||||
character: document.getElementById('ai-character').value,
|
||||
character_info: document.getElementById('ai-character-info').value,
|
||||
character_breakdown: document.getElementById('ai-character-breakdown').checked
|
||||
};
|
||||
|
||||
await fetchAPI('/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ settings })
|
||||
});
|
||||
|
||||
displayFeedback('character-settings-feedback', 'Character settings saved successfully!');
|
||||
} catch (error) {
|
||||
displayFeedback('character-settings-feedback', `Error saving character settings: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear Character button
|
||||
document.getElementById('clear-character-settings-button').addEventListener('click', async () => {
|
||||
if (!confirm('Are you sure you want to clear character settings?')) return;
|
||||
|
||||
try {
|
||||
const settings = {
|
||||
character: null,
|
||||
character_info: null,
|
||||
character_breakdown: false
|
||||
};
|
||||
|
||||
await fetchAPI('/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ settings })
|
||||
});
|
||||
|
||||
// Clear UI
|
||||
document.getElementById('ai-character').value = '';
|
||||
document.getElementById('ai-character-info').value = '';
|
||||
document.getElementById('ai-character-breakdown').checked = false;
|
||||
|
||||
displayFeedback('character-settings-feedback', 'Character settings cleared.');
|
||||
} catch (error) {
|
||||
displayFeedback('character-settings-feedback', `Error clearing character settings: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Save System Prompt button
|
||||
document.getElementById('save-system-prompt-button').addEventListener('click', async () => {
|
||||
try {
|
||||
const settings = {
|
||||
system_message: document.getElementById('ai-system-prompt').value
|
||||
};
|
||||
|
||||
await fetchAPI('/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ settings })
|
||||
});
|
||||
|
||||
displayFeedback('system-prompt-feedback', 'System prompt saved successfully!');
|
||||
} catch (error) {
|
||||
displayFeedback('system-prompt-feedback', `Error saving system prompt: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Reset System Prompt button
|
||||
document.getElementById('reset-system-prompt-button').addEventListener('click', async () => {
|
||||
if (!confirm('Are you sure you want to reset the system prompt to default?')) return;
|
||||
|
||||
try {
|
||||
const settings = {
|
||||
system_message: null
|
||||
};
|
||||
|
||||
await fetchAPI('/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ settings })
|
||||
});
|
||||
|
||||
// Clear UI
|
||||
document.getElementById('ai-system-prompt').value = '';
|
||||
|
||||
displayFeedback('system-prompt-feedback', 'System prompt reset to default.');
|
||||
} catch (error) {
|
||||
displayFeedback('system-prompt-feedback', `Error resetting system prompt: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Save Custom Instructions button
|
||||
document.getElementById('save-custom-instructions-button').addEventListener('click', async () => {
|
||||
try {
|
||||
const settings = {
|
||||
custom_instructions: document.getElementById('ai-custom-instructions').value
|
||||
};
|
||||
|
||||
await fetchAPI('/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ settings })
|
||||
});
|
||||
|
||||
displayFeedback('custom-instructions-feedback', 'Custom instructions saved successfully!');
|
||||
} catch (error) {
|
||||
displayFeedback('custom-instructions-feedback', `Error saving custom instructions: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear Custom Instructions button
|
||||
document.getElementById('clear-custom-instructions-button').addEventListener('click', async () => {
|
||||
if (!confirm('Are you sure you want to clear custom instructions?')) return;
|
||||
|
||||
try {
|
||||
const settings = {
|
||||
custom_instructions: null
|
||||
};
|
||||
|
||||
await fetchAPI('/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ settings })
|
||||
});
|
||||
|
||||
// Clear UI
|
||||
document.getElementById('ai-custom-instructions').value = '';
|
||||
|
||||
displayFeedback('custom-instructions-feedback', 'Custom instructions cleared.');
|
||||
} catch (error) {
|
||||
displayFeedback('custom-instructions-feedback', `Error clearing custom instructions: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
// --- Conversations Functions ---
|
||||
let currentConversations = [];
|
||||
let selectedConversationId = null;
|
||||
|
||||
async function loadConversations() {
|
||||
try {
|
||||
const response = await fetchAPI('/conversations');
|
||||
currentConversations = response.conversations || [];
|
||||
|
||||
renderConversationsList();
|
||||
conversationsLoaded = true;
|
||||
|
||||
if (currentConversations.length === 0) {
|
||||
// Show the "no conversations" message
|
||||
document.querySelector('.no-conversations').style.display = 'block';
|
||||
document.getElementById('conversation-detail').style.display = 'none';
|
||||
} else {
|
||||
document.querySelector('.no-conversations').style.display = 'none';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading conversations:', error);
|
||||
document.querySelector('.no-conversations').textContent = `Error loading conversations: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderConversationsList() {
|
||||
const conversationsList = document.getElementById('conversations-list');
|
||||
const noConversationsMessage = document.querySelector('.no-conversations');
|
||||
|
||||
// Clear existing conversations except the "no conversations" message
|
||||
Array.from(conversationsList.children).forEach(child => {
|
||||
if (!child.classList.contains('no-conversations')) {
|
||||
conversationsList.removeChild(child);
|
||||
}
|
||||
});
|
||||
|
||||
if (currentConversations.length === 0) {
|
||||
noConversationsMessage.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
noConversationsMessage.style.display = 'none';
|
||||
|
||||
// Sort conversations by updated_at (newest first)
|
||||
const sortedConversations = [...currentConversations].sort((a, b) => {
|
||||
return new Date(b.updated_at) - new Date(a.updated_at);
|
||||
});
|
||||
|
||||
// Add conversations to the list
|
||||
sortedConversations.forEach(conversation => {
|
||||
const conversationItem = document.createElement('div');
|
||||
conversationItem.className = 'conversation-item';
|
||||
conversationItem.dataset.id = conversation.id;
|
||||
|
||||
if (conversation.id === selectedConversationId) {
|
||||
conversationItem.classList.add('active');
|
||||
}
|
||||
|
||||
// Get the last message for preview
|
||||
let previewText = 'No messages';
|
||||
if (conversation.messages && conversation.messages.length > 0) {
|
||||
const lastMessage = conversation.messages[conversation.messages.length - 1];
|
||||
previewText = lastMessage.content.substring(0, 100) + (lastMessage.content.length > 100 ? '...' : '');
|
||||
}
|
||||
|
||||
// Format the date
|
||||
const date = new Date(conversation.updated_at);
|
||||
const formattedDate = date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
|
||||
|
||||
conversationItem.innerHTML = `
|
||||
<div class="conversation-item-header">
|
||||
<h4 class="conversation-title">${conversation.title}</h4>
|
||||
<span class="conversation-date">${formattedDate}</span>
|
||||
</div>
|
||||
<div class="conversation-preview">${previewText}</div>
|
||||
`;
|
||||
|
||||
conversationItem.addEventListener('click', () => {
|
||||
// Deselect previously selected conversation
|
||||
const previouslySelected = document.querySelector('.conversation-item.active');
|
||||
if (previouslySelected) {
|
||||
previouslySelected.classList.remove('active');
|
||||
}
|
||||
|
||||
// Select this conversation
|
||||
conversationItem.classList.add('active');
|
||||
selectedConversationId = conversation.id;
|
||||
|
||||
// Show conversation details
|
||||
showConversationDetail(conversation);
|
||||
});
|
||||
|
||||
conversationsList.appendChild(conversationItem);
|
||||
});
|
||||
}
|
||||
|
||||
function showConversationDetail(conversation) {
|
||||
const conversationDetail = document.getElementById('conversation-detail');
|
||||
const conversationTitle = document.getElementById('conversation-title');
|
||||
const conversationMessages = document.getElementById('conversation-messages');
|
||||
|
||||
// Show the detail section
|
||||
conversationDetail.style.display = 'block';
|
||||
|
||||
// Set the title
|
||||
conversationTitle.textContent = conversation.title;
|
||||
|
||||
// Clear existing messages
|
||||
conversationMessages.innerHTML = '';
|
||||
|
||||
// Add messages
|
||||
if (conversation.messages && conversation.messages.length > 0) {
|
||||
conversation.messages.forEach(message => {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `message ${message.role === 'user' ? 'user-message' : 'ai-message'}`;
|
||||
|
||||
messageDiv.innerHTML = `
|
||||
<div class="message-header">${message.role === 'user' ? 'You' : 'AI'}</div>
|
||||
<div class="message-content">${message.content}</div>
|
||||
`;
|
||||
|
||||
conversationMessages.appendChild(messageDiv);
|
||||
});
|
||||
|
||||
// Scroll to the bottom
|
||||
conversationMessages.scrollTop = conversationMessages.scrollHeight;
|
||||
} else {
|
||||
// No messages
|
||||
const emptyMessage = document.createElement('div');
|
||||
emptyMessage.className = 'no-messages';
|
||||
emptyMessage.textContent = 'This conversation has no messages.';
|
||||
conversationMessages.appendChild(emptyMessage);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteConversation(conversationId) {
|
||||
try {
|
||||
await fetchAPI(`/conversations/${conversationId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
// Remove from the current conversations array
|
||||
currentConversations = currentConversations.filter(conv => conv.id !== conversationId);
|
||||
|
||||
// If the deleted conversation was selected, clear the selection
|
||||
if (selectedConversationId === conversationId) {
|
||||
selectedConversationId = null;
|
||||
document.getElementById('conversation-detail').style.display = 'none';
|
||||
}
|
||||
|
||||
// Re-render the list
|
||||
renderConversationsList();
|
||||
|
||||
if (currentConversations.length === 0) {
|
||||
document.querySelector('.no-conversations').style.display = 'block';
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error deleting conversation:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function renameConversation(conversationId, newTitle) {
|
||||
try {
|
||||
// Find the conversation
|
||||
const conversation = currentConversations.find(conv => conv.id === conversationId);
|
||||
if (!conversation) {
|
||||
throw new Error('Conversation not found');
|
||||
}
|
||||
|
||||
// Update the title
|
||||
conversation.title = newTitle;
|
||||
|
||||
// Save to the server
|
||||
await fetchAPI('/conversations', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ conversation })
|
||||
});
|
||||
|
||||
// Re-render the list
|
||||
renderConversationsList();
|
||||
|
||||
// Update the detail view if this conversation is selected
|
||||
if (selectedConversationId === conversationId) {
|
||||
document.getElementById('conversation-title').textContent = newTitle;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error renaming conversation:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function createNewConversation(title) {
|
||||
try {
|
||||
// Create a new conversation object
|
||||
const newConversation = {
|
||||
id: crypto.randomUUID ? crypto.randomUUID() : `conv-${Date.now()}`,
|
||||
title: title || 'New Conversation',
|
||||
messages: [],
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Save to the server
|
||||
const savedConversation = await fetchAPI('/conversations', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ conversation: newConversation })
|
||||
});
|
||||
|
||||
// Add to the current conversations array
|
||||
currentConversations.push(savedConversation);
|
||||
|
||||
// Re-render the list
|
||||
renderConversationsList();
|
||||
|
||||
// Select the new conversation
|
||||
selectedConversationId = savedConversation.id;
|
||||
showConversationDetail(savedConversation);
|
||||
|
||||
// Hide the "no conversations" message
|
||||
document.querySelector('.no-conversations').style.display = 'none';
|
||||
|
||||
return savedConversation;
|
||||
} catch (error) {
|
||||
console.error('Error creating conversation:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function exportConversation(conversation) {
|
||||
// Create a JSON string of the conversation
|
||||
const conversationJson = JSON.stringify(conversation, null, 2);
|
||||
|
||||
// Create a blob with the JSON data
|
||||
const blob = new Blob([conversationJson], { type: 'application/json' });
|
||||
|
||||
// Create a URL for the blob
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Create a temporary link element
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `conversation-${conversation.id}.json`;
|
||||
|
||||
// Append the link to the body
|
||||
document.body.appendChild(link);
|
||||
|
||||
// Click the link to trigger the download
|
||||
link.click();
|
||||
|
||||
// Clean up
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// --- Conversation Event Listeners ---
|
||||
|
||||
// New Conversation button
|
||||
document.getElementById('new-conversation-button').addEventListener('click', () => {
|
||||
// Show the new conversation modal
|
||||
const modal = document.getElementById('new-conversation-modal');
|
||||
modal.style.display = 'block';
|
||||
|
||||
// Focus the input
|
||||
document.getElementById('new-conversation-name').focus();
|
||||
});
|
||||
|
||||
// Create Conversation button (in modal)
|
||||
document.getElementById('create-conversation-button').addEventListener('click', async () => {
|
||||
const title = document.getElementById('new-conversation-name').value.trim() || 'New Conversation';
|
||||
const newConversation = await createNewConversation(title);
|
||||
|
||||
if (newConversation) {
|
||||
// Close the modal
|
||||
document.getElementById('new-conversation-modal').style.display = 'none';
|
||||
|
||||
// Clear the input
|
||||
document.getElementById('new-conversation-name').value = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Cancel Create button (in modal)
|
||||
document.getElementById('cancel-create-button').addEventListener('click', () => {
|
||||
// Close the modal
|
||||
document.getElementById('new-conversation-modal').style.display = 'none';
|
||||
|
||||
// Clear the input
|
||||
document.getElementById('new-conversation-name').value = '';
|
||||
});
|
||||
|
||||
// Close modal buttons
|
||||
document.querySelectorAll('.close-modal').forEach(closeButton => {
|
||||
closeButton.addEventListener('click', () => {
|
||||
// Find the parent modal
|
||||
const modal = closeButton.closest('.modal');
|
||||
modal.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// Delete Conversation button
|
||||
document.getElementById('delete-conversation-button').addEventListener('click', async () => {
|
||||
if (!selectedConversationId) return;
|
||||
|
||||
if (confirm('Are you sure you want to delete this conversation? This action cannot be undone.')) {
|
||||
const success = await deleteConversation(selectedConversationId);
|
||||
|
||||
if (success) {
|
||||
// Hide the detail view
|
||||
document.getElementById('conversation-detail').style.display = 'none';
|
||||
} else {
|
||||
alert('Failed to delete conversation. Please try again.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Rename Conversation button
|
||||
document.getElementById('rename-conversation-button').addEventListener('click', () => {
|
||||
if (!selectedConversationId) return;
|
||||
|
||||
// Show the rename modal
|
||||
const modal = document.getElementById('rename-modal');
|
||||
modal.style.display = 'block';
|
||||
|
||||
// Set the current title as the default value
|
||||
const conversation = currentConversations.find(conv => conv.id === selectedConversationId);
|
||||
if (conversation) {
|
||||
document.getElementById('new-conversation-title').value = conversation.title;
|
||||
}
|
||||
|
||||
// Focus the input
|
||||
document.getElementById('new-conversation-title').focus();
|
||||
});
|
||||
|
||||
// Confirm Rename button (in modal)
|
||||
document.getElementById('confirm-rename-button').addEventListener('click', async () => {
|
||||
if (!selectedConversationId) return;
|
||||
|
||||
const newTitle = document.getElementById('new-conversation-title').value.trim();
|
||||
if (!newTitle) {
|
||||
alert('Please enter a title for the conversation.');
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await renameConversation(selectedConversationId, newTitle);
|
||||
|
||||
if (success) {
|
||||
// Close the modal
|
||||
document.getElementById('rename-modal').style.display = 'none';
|
||||
} else {
|
||||
alert('Failed to rename conversation. Please try again.');
|
||||
}
|
||||
});
|
||||
|
||||
// Cancel Rename button (in modal)
|
||||
document.getElementById('cancel-rename-button').addEventListener('click', () => {
|
||||
// Close the modal
|
||||
document.getElementById('rename-modal').style.display = 'none';
|
||||
});
|
||||
|
||||
// Export Conversation button
|
||||
document.getElementById('export-conversation-button').addEventListener('click', () => {
|
||||
if (!selectedConversationId) return;
|
||||
|
||||
const conversation = currentConversations.find(conv => conv.id === selectedConversationId);
|
||||
if (conversation) {
|
||||
exportConversation(conversation);
|
||||
}
|
||||
});
|
||||
|
||||
// Conversation Search
|
||||
document.getElementById('conversation-search').addEventListener('input', function() {
|
||||
const searchTerm = this.value.toLowerCase().trim();
|
||||
|
||||
if (!searchTerm) {
|
||||
// If search is empty, show all conversations
|
||||
renderConversationsList();
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter conversations by title and content
|
||||
const filteredConversations = currentConversations.filter(conversation => {
|
||||
// Check title
|
||||
if (conversation.title.toLowerCase().includes(searchTerm)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check message content
|
||||
if (conversation.messages && conversation.messages.length > 0) {
|
||||
return conversation.messages.some(message =>
|
||||
message.content.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// Update the current conversations array temporarily for rendering
|
||||
const originalConversations = currentConversations;
|
||||
currentConversations = filteredConversations;
|
||||
|
||||
// Render the filtered list
|
||||
renderConversationsList();
|
||||
|
||||
// Restore the original conversations array
|
||||
currentConversations = originalConversations;
|
||||
});
|
||||
|
||||
// --- Initial Load ---
|
||||
let aiSettingsLoaded = false;
|
||||
let conversationsLoaded = false;
|
||||
|
||||
checkLoginStatus();
|
||||
});
|
||||
|
@ -8,7 +8,7 @@ h1, h2, h3, h4 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#dashboard-section, #settings-form {
|
||||
#dashboard-section {
|
||||
background-color: #fff;
|
||||
padding: 1.5em;
|
||||
border-radius: 8px;
|
||||
@ -16,27 +16,60 @@ h1, h2, h3, h4 {
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.dashboard-section {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
background-color: #fff;
|
||||
padding: 1.5em;
|
||||
border-radius: 8px;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1.5em;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
select,
|
||||
textarea {
|
||||
width: 95%;
|
||||
padding: 8px;
|
||||
margin-bottom: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
margin-top: 1em;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 15px;
|
||||
background-color: #5865F2; /* Discord blue */
|
||||
@ -44,8 +77,6 @@ button {
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin-right: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
@ -59,15 +90,18 @@ button:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
button[id^="disable-"] {
|
||||
button[id^="disable-"],
|
||||
button[id^="reset-"],
|
||||
button[id^="clear-"] {
|
||||
background-color: #ffc107; /* Yellow/Orange */
|
||||
color: #333;
|
||||
}
|
||||
button[id^="disable-"]:hover {
|
||||
button[id^="disable-"]:hover,
|
||||
button[id^="reset-"]:hover,
|
||||
button[id^="clear-"]:hover {
|
||||
background-color: #e0a800;
|
||||
}
|
||||
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
@ -75,6 +109,53 @@ hr {
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.dashboard-nav {
|
||||
display: flex;
|
||||
margin-top: 1em;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
border-bottom: 3px solid transparent;
|
||||
}
|
||||
|
||||
.nav-button:hover {
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.nav-button.active {
|
||||
color: #5865F2;
|
||||
border-bottom: 3px solid #5865F2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Channel Select Container */
|
||||
.channel-select-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.channel-dropdown {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Cogs List */
|
||||
.cogs-container {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #eee;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#cogs-list div {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
@ -113,6 +194,158 @@ hr {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
/* Conversations Section */
|
||||
.conversations-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
flex: 1;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.conversations-list-container {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
#conversations-list {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.conversation-item {
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.conversation-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.conversation-item.active {
|
||||
background-color: #e6f7ff;
|
||||
border-left: 3px solid #5865F2;
|
||||
}
|
||||
|
||||
.conversation-item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.conversation-title {
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.conversation-date {
|
||||
font-size: 0.8em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.conversation-preview {
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.no-conversations {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#conversation-detail {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.conversation-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1em;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.conversation-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.conversation-messages {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
background-color: #e6f7ff;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.ai-message {
|
||||
background-color: #f0f0f0;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.message-header {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fff;
|
||||
margin: 15% auto;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 50%;
|
||||
max-width: 500px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.close-modal {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Feedback messages */
|
||||
p[id$="-feedback"] {
|
||||
|
Loading…
x
Reference in New Issue
Block a user