GitHub Actions
Sync from GitHub (excluding README)
6d43d9c
const API_URL = window.location.origin;
let apiKey = null;
let currentRole = null;
let ws = null;
let statsIntervalId = null;
let wsPingIntervalId = null;
const loginScreen = document.getElementById('login-screen');
const adminScreen = document.getElementById('admin-screen');
const userScreen = document.getElementById('user-screen');
const loginForm = document.getElementById('login-form');
const loginError = document.getElementById('login-error');
const apiKeyInput = document.getElementById('api-key-input');
const logoutButtons = document.querySelectorAll('.logout-btn');
const adminUserInfo = document.getElementById('admin-user-info');
const userInfo = document.getElementById('user-info');
const createKeyBtn = document.getElementById('create-key-btn');
const createKeyModal = document.getElementById('create-key-modal');
const createKeyForm = document.getElementById('create-key-form');
const cancelCreateBtn = document.getElementById('cancel-create-btn');
const keyDetailsModal = document.getElementById('key-details-modal');
const closeDetailsBtn = document.getElementById('close-details-btn');
const createServerKeyModal = document.getElementById('create-server-key-modal');
const createServerKeyForm = document.getElementById('create-server-key-form');
const cancelServerKeyBtn = document.getElementById('cancel-server-key-btn');
const refreshKeyModal = document.getElementById('refresh-key-modal');
const cancelRefreshBtn = document.getElementById('cancel-refresh-btn');
const confirmRefreshBtn = document.getElementById('confirm-refresh-btn');
const adminKeysList = document.getElementById('admin-keys-list');
const userKeysList = document.getElementById('user-keys-list');
const commandForm = document.getElementById('command-form');
const commandInput = document.getElementById('command-input');
const commandHistory = document.getElementById('command-history');
const userEventsList = document.getElementById('user-events-list');
const aiConfigForm = document.getElementById('ai-config-form');
const aiApiUrlInput = document.getElementById('ai-api-url');
const aiModelIdInput = document.getElementById('ai-model-id');
const aiApiKeyInput = document.getElementById('ai-api-key');
const aiApiKeyHint = document.getElementById('ai-api-key-hint');
const aiSystemPromptInput = document.getElementById('ai-system-prompt');
const aiEnabledCheckbox = document.getElementById('ai-enabled');
const aiTestBtn = document.getElementById('ai-test-btn');
const aiDeleteBtn = document.getElementById('ai-delete-btn');
const aiProviderSelect = document.getElementById('ai-provider-select');
const systemLogs = document.getElementById('system-logs');
const clearLogsBtn = document.getElementById('clear-logs-btn');
// AI Providers Configuration
const AI_PROVIDERS = {
openai: {
url: 'https://api.openai.com/v1/chat/completions',
model: 'gpt-3.5-turbo'
},
siliconflow: {
url: 'https://api.siliconflow.cn/v1/chat/completions',
model: 'deepseek-ai/DeepSeek-R1'
},
gemini: {
url: 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions',
model: 'gemini-2.0-flash-exp'
},
deepseek: {
url: 'https://api.deepseek.com/chat/completions',
model: 'deepseek-chat'
},
moonshot: {
url: 'https://api.moonshot.cn/v1/chat/completions',
model: 'moonshot-v1-8k'
},
custom: {
url: '',
model: ''
}
};
if (aiProviderSelect) {
aiProviderSelect.addEventListener('change', (e) => {
const provider = AI_PROVIDERS[e.target.value];
if (provider && e.target.value !== 'custom') {
if (aiApiUrlInput) aiApiUrlInput.value = provider.url;
if (aiModelIdInput) aiModelIdInput.value = provider.model;
}
});
}
const aiStatus = document.getElementById('ai-status');
// Theme Management
const themeToggleBtns = document.querySelectorAll('.theme-toggle');
function initTheme() {
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-theme', savedTheme);
updateThemeIcons(savedTheme);
}
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
updateThemeIcons(newTheme);
}
function updateThemeIcons(theme) {
const text = theme === 'dark' ? '浅色模式' : '深色模式';
themeToggleBtns.forEach(btn => {
btn.textContent = text;
});
}
// Initialize Theme
initTheme();
themeToggleBtns.forEach(btn => {
btn.addEventListener('click', toggleTheme);
});
function authHeaders(key) {
return { Authorization: `Bearer ${key}` };
}
function resetSession() {
apiKey = null;
currentRole = null;
if (statsIntervalId) {
clearInterval(statsIntervalId);
statsIntervalId = null;
}
if (wsPingIntervalId) {
clearInterval(wsPingIntervalId);
wsPingIntervalId = null;
}
if (ws) {
ws.close();
ws = null;
}
if (loginScreen) {
loginScreen.classList.remove('hidden');
}
if (adminScreen) {
adminScreen.classList.add('hidden');
}
if (userScreen) {
userScreen.classList.add('hidden');
}
}
async function detectRole(key) {
const adminResponse = await fetch(`${API_URL}/manage/keys`, {
headers: authHeaders(key)
});
if (adminResponse.ok) {
return 'admin';
}
const regularResponse = await fetch(`${API_URL}/manage/keys/server-keys`, {
headers: authHeaders(key)
});
if (regularResponse.ok) {
return 'regular';
}
return null;
}
function showAdminPanel() {
currentRole = 'admin';
loginScreen.classList.add('hidden');
userScreen.classList.add('hidden');
adminScreen.classList.remove('hidden');
if (adminUserInfo) {
adminUserInfo.textContent = 'Admin Key';
}
loadAdminKeys();
loadAIConfig();
}
function showUserPanel() {
currentRole = 'regular';
loginScreen.classList.add('hidden');
adminScreen.classList.add('hidden');
userScreen.classList.remove('hidden');
if (userInfo) {
userInfo.textContent = 'Regular Key';
}
loadUserServerKeys();
loadStats();
connectWebSocket();
statsIntervalId = setInterval(loadStats, 5000);
}
async function loadAdminKeys() {
if (!adminKeysList || !apiKey) {
return;
}
try {
const response = await fetch(`${API_URL}/manage/keys`, {
headers: authHeaders(apiKey)
});
if (response.ok) {
const keys = await response.json();
renderAdminKeys(keys);
} else {
adminKeysList.innerHTML = '<p>Failed to load keys.</p>';
}
} catch (error) {
adminKeysList.innerHTML = `<p>Failed to load keys: ${error.message}</p>`;
}
}
async function loadUserServerKeys() {
if (!userKeysList || !apiKey) {
return;
}
try {
const response = await fetch(`${API_URL}/manage/keys/server-keys`, {
headers: authHeaders(apiKey)
});
if (response.ok) {
const keys = await response.json();
renderUserServerKeys(keys);
} else {
userKeysList.innerHTML = '<p>Failed to load server keys.</p>';
}
} catch (error) {
userKeysList.innerHTML = `<p>Failed to load server keys: ${error.message}</p>`;
}
}
function renderAdminKeys(keys) {
if (!adminKeysList) {
return;
}
if (!keys.length) {
adminKeysList.innerHTML = '<p>No API keys found.</p>';
return;
}
const adminKeys = keys.filter(k => k.keyType === 'admin');
const regularKeys = keys.filter(k => k.keyType === 'regular');
const serverKeys = keys.filter(k => k.keyType === 'server');
const serverKeysMap = {};
serverKeys.forEach(key => {
if (key.regularKeyId) {
if (!serverKeysMap[key.regularKeyId]) {
serverKeysMap[key.regularKeyId] = [];
}
serverKeysMap[key.regularKeyId].push(key);
}
});
let html = '';
if (adminKeys.length > 0) {
html += '<h3 class="group-title">管理员密钥 (Admin Keys)</h3>';
html += adminKeys.map(key => renderKeyCard(key)).join('');
}
if (regularKeys.length > 0) {
html += '<h3 class="group-title">用户密钥 (Regular Keys)</h3>';
html += regularKeys.map(regularKey => {
const childServerKeys = serverKeysMap[regularKey.id] || [];
return renderRegularKeyCard(regularKey, childServerKeys);
}).join('');
}
const linkedServerKeyIds = new Set(Object.values(serverKeysMap).flat().map(k => k.id));
const orphanServerKeys = serverKeys.filter(k => !linkedServerKeyIds.has(k.id));
if (orphanServerKeys.length > 0) {
html += '<h3 class="group-title">独立服务器密钥 (Orphan Server Keys)</h3>';
html += orphanServerKeys.map(key => renderKeyCard(key)).join('');
}
adminKeysList.innerHTML = html;
}
function toggleGroup(headerElement) {
const nestedContainer = headerElement.nextElementSibling;
const toggleIcon = headerElement.querySelector('.toggle-icon');
if (nestedContainer && nestedContainer.classList.contains('nested-server-keys')) {
nestedContainer.classList.toggle('hidden');
if (toggleIcon) {
toggleIcon.style.transform = nestedContainer.classList.contains('hidden') ? 'rotate(0deg)' : 'rotate(180deg)';
}
}
}
function renderKeyCard(key, isNested = false) {
return `
<div class="key-card ${key.keyType === 'admin' ? 'admin' : ''} ${isNested ? 'nested' : ''}">
<div class="key-info">
<h3>
<span class="key-badge ${key.keyType}">
${key.keyType === 'admin' ? 'Admin' : key.keyType === 'server' ? 'Server' : 'Regular'}
</span>
<span class="key-badge ${key.isActive ? 'active' : 'inactive'}">
${key.isActive ? '已启用' : '已停用'}
</span>
${key.name}
</h3>
<p>ID: ${key.id}</p>
<p>Prefix: ${key.keyPrefix}</p>
${key.serverId ? `<p>Server ID: ${key.serverId}</p>` : ''}
<p>创建时间: ${new Date(key.createdAt).toLocaleString()}</p>
<p>最后使用: ${key.lastUsed ? new Date(key.lastUsed).toLocaleString() : '从未'}</p>
</div>
<div class="key-actions">
${key.isActive
? `<button class="btn-danger" onclick="deactivateKey('${key.id}')">停用</button>`
: `<button class="btn-success" onclick="activateKey('${key.id}')">启用</button>`
}
<button class="btn-secondary" onclick="showRefreshKeyModal('${key.id}', '${key.name}')">刷新</button>
<button class="btn-danger" onclick="deleteKey('${key.id}', '${key.name}')">删除</button>
</div>
</div>
`;
}
function renderRegularKeyCard(regularKey, childServerKeys = []) {
return `
<div class="regular-key-group">
<div class="regular-key-header" onclick="toggleGroup(this)">
<div class="key-card">
<div class="key-info">
<h3>
<span class="key-badge regular">Regular</span>
<span class="key-badge ${regularKey.isActive ? 'active' : 'inactive'}">
${regularKey.isActive ? '已启用' : '已停用'}
</span>
${regularKey.name}
</h3>
<p>ID: ${regularKey.id}</p>
<p>Prefix: ${regularKey.keyPrefix}</p>
${regularKey.serverId ? `<p>Server ID: ${regularKey.serverId}</p>` : ''}
<p>创建时间: ${new Date(regularKey.createdAt).toLocaleString()}</p>
<p>最后使用: ${regularKey.lastUsed ? new Date(regularKey.lastUsed).toLocaleString() : '从未'}</p>
</div>
<div class="key-actions">
${regularKey.isActive
? `<button class="btn-danger" onclick="deactivateKey('${regularKey.id}')">停用</button>`
: `<button class="btn-success" onclick="activateKey('${regularKey.id}')">启用</button>`
}
<button class="btn-secondary" onclick="showRefreshKeyModal('${regularKey.id}', '${regularKey.name}')">刷新</button>
<button class="btn-danger" onclick="deleteKey('${regularKey.id}', '${regularKey.name}')">删除</button>
<button class="btn-primary" onclick="showCreateServerKeyModal('${regularKey.id}', '${regularKey.name}')">添加 Server Key</button>
</div>
</div>
${childServerKeys.length > 0 ? `<span class="toggle-icon">▼</span>` : ''}
</div>
${childServerKeys.length > 0 ? `
<div class="nested-server-keys hidden">
<h4>关联的服务器密钥 (Linked Server Keys)</h4>
${childServerKeys.map(serverKey => renderKeyCard(serverKey, true)).join('')}
</div>
` : ''}
</div>
`;
}
function renderUserServerKeys(keys) {
if (!userKeysList) {
return;
}
if (!keys.length) {
userKeysList.innerHTML = '<p>No server keys available.</p>';
return;
}
userKeysList.innerHTML = keys.map((key) => `
<div class="key-card">
<div class="key-info">
<h3>
<span class="key-badge server">Server</span>
<span class="key-badge ${key.isActive ? 'active' : 'inactive'}">
${key.isActive ? '已启用' : '已停用'}
</span>
${key.name}
</h3>
<p>ID: ${key.id}</p>
<p>Prefix: ${key.keyPrefix}</p>
${key.serverId ? `<p>Server ID: ${key.serverId}</p>` : ''}
<p>创建时间: ${new Date(key.createdAt).toLocaleString()}</p>
<p>最后使用: ${key.lastUsed ? new Date(key.lastUsed).toLocaleString() : '从未'}</p>
</div>
<div class="key-actions">
${key.isActive
? `<button class="btn-danger" onclick="deactivateKey('${key.id}')">停用</button>`
: `<button class="btn-success" onclick="activateKey('${key.id}')">启用</button>`
}
<button class="btn-secondary" onclick="showRefreshKeyModal('${key.id}', '${key.name}')">刷新</button>
</div>
</div>
`).join('');
}
async function activateKey(keyId) {
if (!apiKey) {
return;
}
try {
const response = await fetch(`${API_URL}/manage/keys/${keyId}/activate`, {
method: 'PATCH',
headers: authHeaders(apiKey)
});
if (!response.ok) {
const error = await response.json();
alert(`Failed to activate key: ${error.detail}`);
return;
}
if (currentRole === 'admin') {
loadAdminKeys();
} else if (currentRole === 'regular') {
loadUserServerKeys();
}
} catch (error) {
alert(`Failed to activate key: ${error.message}`);
}
}
async function deactivateKey(keyId) {
if (!apiKey) {
return;
}
try {
const response = await fetch(`${API_URL}/manage/keys/${keyId}/deactivate`, {
method: 'PATCH',
headers: authHeaders(apiKey)
});
if (!response.ok) {
const error = await response.json();
alert(`Failed to deactivate key: ${error.detail}`);
return;
}
if (currentRole === 'admin') {
loadAdminKeys();
} else if (currentRole === 'regular') {
loadUserServerKeys();
}
} catch (error) {
alert(`Failed to deactivate key: ${error.message}`);
}
}
async function deleteKey(keyId, keyName) {
if (currentRole !== 'admin') {
return;
}
if (!confirm(`确定要删除密钥 "${keyName}" 吗? 此操作无法撤销。`)) {
return;
}
try {
const response = await fetch(`${API_URL}/manage/keys/${keyId}`, {
method: 'DELETE',
headers: authHeaders(apiKey)
});
if (!response.ok) {
const error = await response.json();
alert(`Failed to delete key: ${error.detail}`);
return;
}
loadAdminKeys();
} catch (error) {
alert(`Failed to delete key: ${error.message}`);
}
}
function showKeyCreatedModal(payload) {
if (!keyDetailsModal) {
return;
}
let content = '';
if (payload.regularKey && payload.serverKey) {
content = `
<p><strong>Regular Key</strong></p>
<p>Name: ${payload.regularKey.name}</p>
<p>Type: ${payload.regularKey.keyType}</p>
<p>ID: ${payload.regularKey.id}</p>
<p>Key: ${payload.regularKey.key}</p>
<hr>
<p><strong>Server Key</strong></p>
<p>Name: ${payload.serverKey.name}</p>
<p>Type: ${payload.serverKey.keyType}</p>
<p>ID: ${payload.serverKey.id}</p>
<p>Key: ${payload.serverKey.key}</p>
`;
} else {
content = `
<p><strong>Key Created</strong></p>
<p>Name: ${payload.name}</p>
<p>Type: ${payload.keyType}</p>
<p>ID: ${payload.id}</p>
<p>Key: ${payload.key}</p>
`;
}
const detailsContent = document.getElementById('key-details-content');
if (detailsContent) {
detailsContent.innerHTML = content;
}
keyDetailsModal.classList.remove('hidden');
}
async function loadStats() {
try {
const response = await fetch(`${API_URL}/health`);
if (!response.ok) {
return;
}
const data = await response.json();
const connections = document.getElementById('user-stat-connections');
if (connections) {
connections.textContent = data.active_ws || 0;
}
const serverKeys = document.getElementById('user-stat-server-keys');
if (serverKeys) {
serverKeys.textContent = data.server_active || 0;
}
} catch (error) {
console.error('Failed to load stats:', error);
}
}
function connectWebSocket() {
if (!apiKey) {
return;
}
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
const wsUrl = `${protocol}://${window.location.host}/ws?api_key=${apiKey}`;
ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('WebSocket connected');
};
ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
if (message.type === 'minecraft_event') {
addEventToList(message.event);
}
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('WebSocket disconnected');
if (wsPingIntervalId) {
clearInterval(wsPingIntervalId);
wsPingIntervalId = null;
}
if (apiKey && currentRole === 'regular') {
setTimeout(() => connectWebSocket(), 5000);
}
};
wsPingIntervalId = setInterval(() => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
}
function addEventToList(event) {
if (!userEventsList) {
return;
}
const eventItem = document.createElement('div');
eventItem.className = 'event-item';
eventItem.innerHTML = `
<strong>${event.event_type}</strong> - ${event.server_name}<br>
<small>${new Date(event.timestamp).toLocaleString()}</small><br>
<pre>${JSON.stringify(event.data, null, 2)}</pre>
`;
userEventsList.insertBefore(eventItem, userEventsList.firstChild);
while (userEventsList.children.length > 50) {
userEventsList.removeChild(userEventsList.lastChild);
}
}
function appendCommandHistory(command, status, detail) {
if (!commandHistory) {
return;
}
const entry = document.createElement('div');
entry.className = 'command-item';
entry.innerHTML = `
<div>${status}: ${command}</div>
${detail ? `<div class="timestamp">${detail}</div>` : ''}
`;
commandHistory.insertBefore(entry, commandHistory.firstChild);
while (commandHistory.children.length > 20) {
commandHistory.removeChild(commandHistory.lastChild);
}
}
if (loginForm) {
loginForm.addEventListener('submit', async (event) => {
event.preventDefault();
const key = apiKeyInput.value.trim();
loginError.textContent = '';
if (!key) {
loginError.textContent = 'API key is required.';
return;
}
try {
const role = await detectRole(key);
if (!role) {
loginError.textContent = 'Invalid key or insufficient permissions.';
return;
}
apiKey = key;
if (role === 'admin') {
showAdminPanel();
} else if (role === 'regular') {
showUserPanel();
}
} catch (error) {
loginError.textContent = error.message || 'Unable to connect to server.';
}
});
}
logoutButtons.forEach((button) => {
button.addEventListener('click', () => {
resetSession();
});
});
if (createKeyBtn) {
createKeyBtn.addEventListener('click', () => {
createKeyModal.classList.remove('hidden');
});
}
if (cancelCreateBtn) {
cancelCreateBtn.addEventListener('click', () => {
createKeyModal.classList.add('hidden');
createKeyForm.reset();
});
}
if (closeDetailsBtn) {
closeDetailsBtn.addEventListener('click', () => {
keyDetailsModal.classList.add('hidden');
});
}
function showCreateServerKeyModal(regularKeyId, regularKeyName) {
if (!createServerKeyModal) {
return;
}
const parentNameEl = document.getElementById('create-server-key-parent-name');
if (parentNameEl) {
parentNameEl.textContent = `父级 Regular Key: ${regularKeyName}`;
}
const regularIdInput = document.getElementById('server-key-regular-id');
if (regularIdInput) {
regularIdInput.value = regularKeyId;
}
createServerKeyModal.classList.remove('hidden');
}
function hideCreateServerKeyModal() {
if (!createServerKeyModal) {
return;
}
createServerKeyModal.classList.add('hidden');
const nameInput = document.getElementById('server-key-name');
const descInput = document.getElementById('server-key-description');
const serverIdInput = document.getElementById('server-key-server-id');
if (nameInput) nameInput.value = '';
if (descInput) descInput.value = '';
if (serverIdInput) serverIdInput.value = '';
}
async function createServerKey(event) {
event.preventDefault();
const regularKeyId = document.getElementById('server-key-regular-id')?.value;
const name = document.getElementById('server-key-name')?.value.trim();
const description = document.getElementById('server-key-description')?.value.trim();
const serverId = document.getElementById('server-key-server-id')?.value.trim();
if (!regularKeyId || !name) {
alert('Regular Key ID and name are required.');
return;
}
try {
const payload = {
name,
description,
regular_key_id: regularKeyId
};
if (serverId) {
payload.server_id = serverId;
}
const response = await fetch(`${API_URL}/manage/keys/server-keys`, {
method: 'POST',
headers: {
...authHeaders(apiKey),
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
const error = await response.json();
alert(`Failed to create server key: ${error.detail}`);
return;
}
const result = await response.json();
hideCreateServerKeyModal();
showKeyCreatedModal(result);
loadAdminKeys();
} catch (error) {
alert(`Failed to create server key: ${error.message}`);
}
}
if (cancelServerKeyBtn) {
cancelServerKeyBtn.addEventListener('click', hideCreateServerKeyModal);
}
if (createServerKeyForm) {
createServerKeyForm.addEventListener('submit', createServerKey);
}
window.showCreateServerKeyModal = showCreateServerKeyModal;
window.hideCreateServerKeyModal = hideCreateServerKeyModal;
window.createServerKey = createServerKey;
function showRefreshKeyModal(keyId, keyName) {
if (!refreshKeyModal) {
return;
}
const keyNameEl = document.getElementById('refresh-key-name');
const keyIdInput = document.getElementById('refresh-key-id');
if (keyNameEl) keyNameEl.textContent = keyName;
if (keyIdInput) keyIdInput.value = keyId;
refreshKeyModal.classList.remove('hidden');
}
function hideRefreshKeyModal() {
if (!refreshKeyModal) {
return;
}
refreshKeyModal.classList.add('hidden');
}
async function refreshKey() {
const keyId = document.getElementById('refresh-key-id')?.value;
if (!keyId || !apiKey) {
return;
}
try {
const response = await fetch(`${API_URL}/manage/keys/${keyId}/refresh`, {
method: 'POST',
headers: authHeaders(apiKey)
});
if (!response.ok) {
const error = await response.json();
alert(`刷新失败: ${error.detail}`);
return;
}
const result = await response.json();
hideRefreshKeyModal();
showKeyCreatedModal(result);
if (currentRole === 'admin') {
loadAdminKeys();
} else if (currentRole === 'regular') {
loadUserServerKeys();
}
} catch (error) {
alert(`刷新失败: ${error.message}`);
}
}
if (cancelRefreshBtn) {
cancelRefreshBtn.addEventListener('click', hideRefreshKeyModal);
}
if (confirmRefreshBtn) {
confirmRefreshBtn.addEventListener('click', refreshKey);
}
window.showRefreshKeyModal = showRefreshKeyModal;
window.hideRefreshKeyModal = hideRefreshKeyModal;
window.refreshKey = refreshKey;
if (createKeyForm) {
createKeyForm.addEventListener('submit', async (event) => {
event.preventDefault();
const name = document.getElementById('key-name').value.trim();
const description = document.getElementById('key-description').value.trim();
const keyType = document.getElementById('key-type').value;
const serverId = document.getElementById('key-server-id').value.trim();
if (!name) {
alert('Name is required.');
return;
}
try {
const payload = {
name,
description,
key_type: keyType
};
if (serverId) {
payload.server_id = serverId;
}
const response = await fetch(`${API_URL}/manage/keys`, {
method: 'POST',
headers: {
...authHeaders(apiKey),
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
const error = await response.json();
alert(`Failed to create key: ${error.detail}`);
return;
}
const result = await response.json();
createKeyModal.classList.add('hidden');
createKeyForm.reset();
showKeyCreatedModal(result);
loadAdminKeys();
} catch (error) {
alert(`Failed to create key: ${error.message}`);
}
});
}
if (commandForm) {
commandForm.addEventListener('submit', async (event) => {
event.preventDefault();
const command = commandInput.value.trim();
if (!command) {
return;
}
try {
const response = await fetch(`${API_URL}/api/server/command`, {
method: 'POST',
headers: {
...authHeaders(apiKey),
'Content-Type': 'application/json'
},
body: JSON.stringify({ command })
});
if (!response.ok) {
const error = await response.json();
appendCommandHistory(command, 'Rejected', error.detail);
return;
}
appendCommandHistory(command, 'Sent', new Date().toLocaleString());
commandInput.value = '';
} catch (error) {
appendCommandHistory(command, 'Error', error.message);
}
});
}
window.activateKey = activateKey;
window.deactivateKey = deactivateKey;
window.deleteKey = deleteKey;
async function loadAIConfig() {
if (!apiKey) {
return;
}
try {
const response = await fetch(`${API_URL}/api/ai/config`, {
headers: authHeaders(apiKey)
});
if (response.ok) {
const config = await response.json();
if (aiApiUrlInput) aiApiUrlInput.value = config.apiUrl || '';
if (aiModelIdInput) aiModelIdInput.value = config.modelId || '';
if (aiApiKeyInput) aiApiKeyInput.value = '';
if (aiApiKeyHint) aiApiKeyHint.textContent = config.apiKey ? `Current: ${config.apiKey}` : '';
if (aiSystemPromptInput) aiSystemPromptInput.value = config.systemPrompt || '';
if (aiEnabledCheckbox) aiEnabledCheckbox.checked = config.enabled;
showAIStatus('Configuration loaded', 'success');
} else if (response.status === 404) {
if (aiApiUrlInput) aiApiUrlInput.value = '';
if (aiModelIdInput) aiModelIdInput.value = '';
if (aiApiKeyInput) aiApiKeyInput.value = '';
if (aiApiKeyHint) aiApiKeyHint.textContent = '';
if (aiSystemPromptInput) aiSystemPromptInput.value = '';
if (aiEnabledCheckbox) aiEnabledCheckbox.checked = true;
showAIStatus('No configuration found. Please set up AI configuration.', 'info');
}
} catch (error) {
showAIStatus(`Failed to load AI config: ${error.message}`, 'error');
}
}
async function saveAIConfig(event) {
event.preventDefault();
if (!apiKey) {
return;
}
const apiUrl = aiApiUrlInput?.value?.trim();
const modelId = aiModelIdInput?.value?.trim();
const apiKeyValue = aiApiKeyInput?.value?.trim();
const systemPrompt = aiSystemPromptInput?.value?.trim() || null;
const enabled = aiEnabledCheckbox?.checked ?? true;
if (!apiUrl || !modelId) {
showAIStatus('API URL and Model ID are required', 'error');
return;
}
try {
const existingResponse = await fetch(`${API_URL}/api/ai/config`, {
headers: authHeaders(apiKey)
});
const isUpdate = existingResponse.ok;
const payload = {
api_url: apiUrl,
model_id: modelId,
enabled: enabled,
system_prompt: systemPrompt
};
if (apiKeyValue) {
payload.api_key = apiKeyValue;
} else if (!isUpdate) {
showAIStatus('API Key is required for new configuration', 'error');
return;
}
const method = isUpdate ? 'PATCH' : 'POST';
const response = await fetch(`${API_URL}/api/ai/config`, {
method: method,
headers: {
...authHeaders(apiKey),
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (response.ok) {
const config = await response.json();
if (aiApiKeyInput) aiApiKeyInput.value = '';
if (aiApiKeyHint) aiApiKeyHint.textContent = config.apiKey ? `Current: ${config.apiKey}` : '';
showAIStatus('Configuration saved successfully', 'success');
} else {
const error = await response.json();
showAIStatus(`Failed to save: ${error.detail}`, 'error');
}
} catch (error) {
showAIStatus(`Failed to save AI config: ${error.message}`, 'error');
}
}
async function testAIConnection() {
if (!apiKey) {
return;
}
showAIStatus('Testing connection...', 'info');
try {
const response = await fetch(`${API_URL}/api/ai/config/test`, {
method: 'POST',
headers: authHeaders(apiKey)
});
const result = await response.json();
if (result.success) {
showAIStatus(`Connection successful! Model: ${result.model}, Response: ${result.response}`, 'success');
} else {
showAIStatus(`Connection failed: ${result.error}`, 'error');
}
} catch (error) {
showAIStatus(`Test failed: ${error.message}`, 'error');
}
}
async function deleteAIConfig() {
if (!apiKey) {
return;
}
if (!confirm('确定要删除 AI 配置吗?')) {
return;
}
try {
const response = await fetch(`${API_URL}/api/ai/config`, {
method: 'DELETE',
headers: authHeaders(apiKey)
});
if (response.ok || response.status === 204) {
if (aiApiUrlInput) aiApiUrlInput.value = '';
if (aiModelIdInput) aiModelIdInput.value = '';
if (aiApiKeyInput) aiApiKeyInput.value = '';
if (aiApiKeyHint) aiApiKeyHint.textContent = '';
if (aiSystemPromptInput) aiSystemPromptInput.value = '';
if (aiEnabledCheckbox) aiEnabledCheckbox.checked = true;
showAIStatus('Configuration deleted', 'success');
} else {
const error = await response.json();
showAIStatus(`Failed to delete: ${error.detail}`, 'error');
}
} catch (error) {
showAIStatus(`Failed to delete AI config: ${error.message}`, 'error');
}
}
function showAIStatus(message, type) {
if (!aiStatus) {
return;
}
aiStatus.textContent = message;
aiStatus.className = `ai-status ${type}`;
}
if (aiConfigForm) {
aiConfigForm.addEventListener('submit', saveAIConfig);
}
if (aiTestBtn) {
aiTestBtn.addEventListener('click', testAIConnection);
}
if (aiDeleteBtn) {
aiDeleteBtn.addEventListener('click', deleteAIConfig);
}