Files
ai-chat-app/www/admin.js
hubian f85991064f feat: 智能体管理增加上线开关、标签、可使用工具
智能体新增功能:
- 上线开关(is_online):点击快速切换,未上线则APP不可见
- 标签(tags):支持多标签,逗号分隔(hot/popular/new等)
- 可使用工具(enable_tools):配置智能体可用的工具列表

类别调整:
- 热门改为标签,不再是类别
- 类别改为: basic/work/study/life
- hot=热门标签, popular=推荐标签, new=新品标签

数据库变更:
- agents表新增 is_online 字段
- agents表新增 enable_tools 字段
- 移除 enable_search 字段(改为工具)

API新增:
- /api/admin/agents/<id>/online 切换上线状态
- 前端配置只返回上线且活跃的智能体
2026-04-27 14:15:14 +08:00

1126 lines
42 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// AI助手 - 后台管理前端
const API_BASE = '';
let currentPage = 'stats';
let llmConfigs = [];
let agents = [];
let toolConfigs = [];
let chatConfigs = [];
let systemConfigs = {};
// ==================== 登录 ====================
document.getElementById('loginBtn').addEventListener('click', async () => {
const username = document.getElementById('loginUsername').value.trim();
const password = document.getElementById('loginPassword').value;
if (!username || !password) {
showToast('请输入用户名和密码');
return;
}
try {
const res = await fetch(`${API_BASE}/api/admin/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await res.json();
if (data.success) {
localStorage.setItem('adminLoggedIn', 'true');
localStorage.setItem('adminUsername', username);
document.getElementById('loginPage').style.display = 'none';
document.getElementById('adminPage').style.display = 'block';
document.getElementById('adminName').textContent = username;
loadPage('stats');
} else {
showToast(data.error || '登录失败');
}
} catch (e) {
showToast('登录失败,请检查网络');
}
});
// 检查登录状态
if (localStorage.getItem('adminLoggedIn') === 'true') {
document.getElementById('loginPage').style.display = 'none';
document.getElementById('adminPage').style.display = 'block';
document.getElementById('adminName').textContent = localStorage.getItem('adminUsername');
loadPage('stats');
}
// 退出登录
document.getElementById('logoutBtn').addEventListener('click', () => {
localStorage.removeItem('adminLoggedIn');
localStorage.removeItem('adminUsername');
document.getElementById('adminPage').style.display = 'none';
document.getElementById('loginPage').style.display = 'flex';
});
// ==================== 侧边栏导航 ====================
document.querySelectorAll('.sidebar-item').forEach(item => {
item.addEventListener('click', () => {
const page = item.getAttribute('data-page');
document.querySelectorAll('.sidebar-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
loadPage(page);
});
});
// ==================== 页面加载 ====================
async function loadPage(page) {
currentPage = page;
const content = document.getElementById('mainContent');
switch (page) {
case 'stats':
await loadStatsPage(content);
break;
case 'llm':
await loadLLMPage(content);
break;
case 'agents':
await loadAgentsPage(content);
break;
case 'chat':
await loadChatConfigPage(content);
break;
case 'tools':
await loadToolsPage(content);
break;
case 'system':
await loadSystemPage(content);
break;
}
}
// ==================== 统计页面 ====================
async function loadStatsPage(content) {
const stats = await fetchAPI('/api/admin/stats');
content.innerHTML = `
<div class="content-header">
<h1 class="content-title">统计信息</h1>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">🧠</div>
<div class="stat-value">${stats.llm_total_calls || 0}</div>
<div class="stat-label">LLM 总调用</div>
</div>
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-value">${stats.llm_today_calls || 0}</div>
<div class="stat-label">今日 LLM 调用</div>
</div>
<div class="stat-card">
<div class="stat-icon">🔍</div>
<div class="stat-value">${stats.search_total_calls || 0}</div>
<div class="stat-label">搜索总调用</div>
</div>
<div class="stat-card">
<div class="stat-icon">📈</div>
<div class="stat-value">${stats.search_today_calls || 0}</div>
<div class="stat-label">今日搜索</div>
</div>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">🤖</div>
<div class="stat-value">${stats.agent_count || 0}</div>
<div class="stat-label">智能体数量</div>
</div>
<div class="stat-card">
<div class="stat-icon">🧠</div>
<div class="stat-value">${stats.llm_count || 0}</div>
<div class="stat-label">LLM 配置数量</div>
</div>
<div class="stat-card">
<div class="stat-icon">👥</div>
<div class="stat-value">${stats.total_users || 0}</div>
<div class="stat-label">注册用户数</div>
</div>
<div class="stat-card">
<div class="stat-icon">📅</div>
<div class="stat-value">${new Date().toLocaleDateString()}</div>
<div class="stat-label">统计日期</div>
</div>
</div>
<div class="data-table" style="margin-top: 24px;">
<h3 style="padding: 16px; background: var(--bg-color);">热门智能体使用排行</h3>
<table>
<thead>
<tr>
<th>智能体</th>
<th>使用次数</th>
</tr>
</thead>
<tbody>
${stats.agent_usage?.map(a => `
<tr>
<td>${a.log_key}</td>
<td>${a.count}</td>
</tr>
`).join('') || '<tr><td colspan="2">暂无数据</td></tr>'}
</tbody>
</table>
</div>
`;
}
// ==================== 大模型配置页面 ====================
async function loadLLMPage(content) {
llmConfigs = await fetchAPI('/api/admin/llm');
content.innerHTML = `
<div class="content-header">
<h1 class="content-title">大模型配置</h1>
<button class="add-btn" onclick="showAddLLMModal()">+ 添加配置</button>
</div>
<div class="data-table">
<table>
<thead>
<tr>
<th>名称</th>
<th>提供商</th>
<th>模型</th>
<th>API URL</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${llmConfigs.map(c => `
<tr>
<td>${c.name} ${c.is_default ? '<span class="default-badge">默认</span>' : ''}</td>
<td>${c.provider}</td>
<td>${c.model}</td>
<td style="max-width: 200px; overflow: hidden; text-overflow: ellipsis;">${c.api_url}</td>
<td>${c.is_active ? '✅ 启用' : '❌ 禁用'}</td>
<td>
<div class="action-btns">
<button class="action-btn edit" onclick="showEditLLMModal(${c.id})">编辑</button>
${!c.is_default ? `<button class="action-btn default" onclick="setDefaultLLM(${c.id})">设为默认</button>` : ''}
<button class="action-btn delete" onclick="deleteLLM(${c.id})">删除</button>
</div>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
`;
}
function showAddLLMModal() {
showModal('添加大模型配置', `
<div class="form-group">
<label class="form-label">名称</label>
<input type="text" class="form-input" id="llmName" placeholder="如智谱GLM">
</div>
<div class="form-group">
<label class="form-label">提供商</label>
<select class="form-select" id="llmProvider">
<option value="zhipu">智谱</option>
<option value="openai">OpenAI</option>
<option value="anthropic">Anthropic</option>
<option value="custom">自定义</option>
</select>
</div>
<div class="form-group">
<label class="form-label">API URL</label>
<input type="text" class="form-input" id="llmApiUrl" placeholder="API endpoint URL">
</div>
<div class="form-group">
<label class="form-label">API Key</label>
<input type="text" class="form-input" id="llmApiKey" placeholder="API Key">
</div>
<div class="form-group">
<label class="form-label">模型</label>
<input type="text" class="form-input" id="llmModel" placeholder="如glm-4.5-air">
</div>
<div class="form-group">
<label class="form-label">Max Tokens</label>
<input type="number" class="form-input" id="llmMaxTokens" value="2048">
</div>
<div class="form-group">
<label class="form-label">Temperature</label>
<input type="number" class="form-input" id="llmTemperature" value="0.7" step="0.1">
</div>
<button class="form-submit" onclick="saveLLM()">保存</button>
`);
}
function showEditLLMModal(id) {
const config = llmConfigs.find(c => c.id === id);
if (!config) return;
showModal('编辑大模型配置', `
<div class="form-group">
<label class="form-label">名称</label>
<input type="text" class="form-input" id="llmName" value="${config.name}">
</div>
<div class="form-group">
<label class="form-label">提供商</label>
<select class="form-select" id="llmProvider">
<option value="zhipu" ${config.provider === 'zhipu' ? 'selected' : ''}>智谱</option>
<option value="openai" ${config.provider === 'openai' ? 'selected' : ''}>OpenAI</option>
<option value="anthropic" ${config.provider === 'anthropic' ? 'selected' : ''}>Anthropic</option>
<option value="custom" ${config.provider === 'custom' ? 'selected' : ''}>自定义</option>
</select>
</div>
<div class="form-group">
<label class="form-label">API URL</label>
<input type="text" class="form-input" id="llmApiUrl" value="${config.api_url}">
</div>
<div class="form-group">
<label class="form-label">API Key</label>
<input type="text" class="form-input" id="llmApiKey" value="${config.api_key}">
</div>
<div class="form-group">
<label class="form-label">模型</label>
<input type="text" class="form-input" id="llmModel" value="${config.model}">
</div>
<div class="form-group">
<label class="form-label">Max Tokens</label>
<input type="number" class="form-input" id="llmMaxTokens" value="${config.max_tokens}">
</div>
<div class="form-group">
<label class="form-label">Temperature</label>
<input type="number" class="form-input" id="llmTemperature" value="${config.temperature}" step="0.1">
</div>
<button class="form-submit" onclick="updateLLM(${id})">保存</button>
`);
}
async function saveLLM() {
const data = {
name: document.getElementById('llmName').value,
provider: document.getElementById('llmProvider').value,
api_url: document.getElementById('llmApiUrl').value,
api_key: document.getElementById('llmApiKey').value,
model: document.getElementById('llmModel').value,
max_tokens: parseInt(document.getElementById('llmMaxTokens').value),
temperature: parseFloat(document.getElementById('llmTemperature').value)
};
if (!data.name || !data.api_url || !data.api_key || !data.model) {
showToast('请填写完整信息');
return;
}
await fetchAPI('/api/admin/llm', 'POST', data);
closeModal();
showToast('添加成功');
loadPage('llm');
}
async function updateLLM(id) {
const data = {
name: document.getElementById('llmName').value,
provider: document.getElementById('llmProvider').value,
api_url: document.getElementById('llmApiUrl').value,
api_key: document.getElementById('llmApiKey').value,
model: document.getElementById('llmModel').value,
max_tokens: parseInt(document.getElementById('llmMaxTokens').value),
temperature: parseFloat(document.getElementById('llmTemperature').value)
};
await fetchAPI(`/api/admin/llm/${id}`, 'PUT', data);
closeModal();
showToast('更新成功');
loadPage('llm');
}
async function setDefaultLLM(id) {
await fetchAPI(`/api/admin/llm/${id}/default`, 'POST');
showToast('已设为默认');
loadPage('llm');
}
async function deleteLLM(id) {
if (!confirm('确定删除此配置?')) return;
await fetchAPI(`/api/admin/llm/${id}`, 'DELETE');
showToast('删除成功');
loadPage('llm');
}
// ==================== 智能体管理页面 ====================
async function loadAgentsPage(content) {
agents = await fetchAPI('/api/admin/agents');
llmConfigs = await fetchAPI('/api/admin/llm');
toolConfigs = await fetchAPI('/api/admin/tools');
const categories = {
basic: '基础',
work: '工作',
study: '学习',
life: '生活'
};
const tagLabels = {
'hot': '🔥 热门',
'popular': '👍 推荐',
'new': '🆕 新品',
'': ''
};
content.innerHTML = `
<div class="content-header">
<h1 class="content-title">智能体管理</h1>
<button class="add-btn" onclick="showAddAgentModal()">+ 添加智能体</button>
</div>
<div class="data-table">
<table>
<thead>
<tr>
<th>头像</th>
<th>名称</th>
<th>类别</th>
<th>标签</th>
<th>热度</th>
<th>上线状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${agents.map(a => `
<tr>
<td style="font-size: 24px;">${a.avatar}</td>
<td>${a.name}</td>
<td>${categories[a.category] || a.category}</td>
<td>${a.tags ? a.tags.split(',').map(t => tagLabels[t] || t).filter(t => t).join(' ') : '-'}</td>
<td>${a.heat || 0}</td>
<td>
<span style="cursor: pointer; color: ${a.is_online ? '#22c55e' : '#e53e3e'};" onclick="toggleAgentOnline('${a.agent_id}', ${a.is_online})">
${a.is_online ? '✅ 已上线' : '❌ 未上线'}
</span>
</td>
<td>
<div class="action-btns">
<button class="action-btn edit" onclick="showEditAgentModal('${a.agent_id}')">编辑</button>
<button class="action-btn delete" onclick="deleteAgent('${a.agent_id}')">删除</button>
</div>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
<div style="margin-top: 24px; padding: 16px; background: white; border-radius: 12px;">
<h3 style="margin-bottom: 16px;">说明</h3>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;">
<div style="padding: 12px; background: #f5f7fa; border-radius: 8px;">
<strong>上线状态</strong>
<p style="color: #718096; font-size: 12px; margin-top: 4px;">点击可快速切换未上线则APP不可见</p>
</div>
<div style="padding: 12px; background: #f5f7fa; border-radius: 8px;">
<strong>标签</strong>
<p style="color: #718096; font-size: 12px; margin-top: 4px;">hot=热门, popular=推荐, new=新品</p>
</div>
<div style="padding: 12px; background: #f5f7fa; border-radius: 8px;">
<strong>类别</strong>
<p style="color: #718096; font-size: 12px; margin-top: 4px;">basic/work/study/life</p>
</div>
</div>
</div>
`;
}
async function toggleAgentOnline(agentId, currentStatus) {
const newStatus = currentStatus ? 0 : 1;
await fetchAPI(`/api/admin/agents/${agentId}/online`, 'POST', { is_online: newStatus });
showToast(newStatus ? '已上线' : '已下线');
loadPage('agents');
}
function showAddAgentModal() {
showModal('添加智能体', `
<div class="form-group">
<label class="form-label">ID</label>
<input type="text" class="form-input" id="agentId" placeholder="如assistant">
</div>
<div class="form-group">
<label class="form-label">名称</label>
<input type="text" class="form-input" id="agentName" placeholder="如:通用助手">
</div>
<div class="form-group">
<label class="form-label">头像</label>
<select class="form-select" id="agentAvatar">
${['🤖', '✍️', '👨‍💻', '🌐', '💼', '📊', '📈', '📚', '🔤', '🔢', '🏥', '✈️', '🍳', '🧑', '😊', '😎', '🤓', '🦸', '🧙', '🥷'].map(a =>
`<option value="${a}">${a}</option>`
).join('')}
</select>
</div>
<div class="form-group">
<label class="form-label">类别</label>
<select class="form-select" id="agentCategory">
<option value="basic">基础</option>
<option value="work">工作</option>
<option value="study">学习</option>
<option value="life">生活</option>
</select>
</div>
<div class="form-group">
<label class="form-label">标签(逗号分隔)</label>
<input type="text" class="form-input" id="agentTags" placeholder="如hot,popular">
<span style="color: #999; font-size: 12px;">可选: hot(热门), popular(推荐), new(新品)</span>
</div>
<div class="form-group">
<label class="form-label">描述</label>
<input type="text" class="form-input" id="agentDescription" placeholder="智能体描述">
</div>
<div class="form-group">
<label class="form-label">System Prompt</label>
<textarea class="form-textarea" id="agentPrompt" placeholder="系统提示词"></textarea>
</div>
<div class="form-group">
<label class="form-label">LLM配置可选</label>
<select class="form-select" id="agentLLM">
<option value="">使用默认</option>
${llmConfigs.map(c => `<option value="${c.id}">${c.name}</option>`).join('')}
</select>
</div>
<div class="form-group">
<label class="form-label">可使用工具(逗号分隔)</label>
<input type="text" class="form-input" id="agentEnableTools" placeholder="如search,calculator">
<span style="color: #999; font-size: 12px;">已配置工具: ${toolConfigs.map(t => t.tool_id).join(', ')}</span>
</div>
<div class="form-group">
<label class="form-label">热度</label>
<input type="number" class="form-input" id="agentHeat" value="0">
</div>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="agentIsOnline" checked> 立即上线
</label>
</div>
<button class="form-submit" onclick="saveAgent()">保存</button>
`);
}
</select>
</div>
<div class="form-group">
<label class="form-label">描述</label>
<input type="text" class="form-input" id="agentDescription" placeholder="智能体描述">
</div>
<div class="form-group">
<label class="form-label">System Prompt</label>
<textarea class="form-textarea" id="agentPrompt" placeholder="系统提示词"></textarea>
</div>
<div class="form-group">
<label class="form-label">LLM配置</label>
<select class="form-select" id="agentLLM">
<option value="">使用默认</option>
${llmConfigs.map(c => `<option value="${c.id}">${c.name}</option>`).join('')}
</select>
</div>
<div class="form-group">
<label class="form-label">热度</label>
<input type="number" class="form-input" id="agentHeat" value="0">
</div>
<div class="form-group">
<label class="form-label">标签</label>
<input type="text" class="form-input" id="agentTags" placeholder="多个标签用逗号分隔">
</div>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="agentEnableSearch"> 启用联网搜索
</label>
</div>
<button class="form-submit" onclick="saveAgent()">保存</button>
`);
}
function showEditAgentModal(agentId) {
const agent = agents.find(a => a.agent_id === agentId);
if (!agent) return;
showModal('编辑智能体', `
<div class="form-group">
<label class="form-label">ID不可修改</label>
<input type="text" class="form-input" id="agentId" value="${agent.agent_id}" readonly style="background: #f5f7fa;">
</div>
<div class="form-group">
<label class="form-label">名称</label>
<input type="text" class="form-input" id="agentName" value="${agent.name}">
</div>
<div class="form-group">
<label class="form-label">头像</label>
<select class="form-select" id="agentAvatar">
${['🤖', '✍️', '👨‍💻', '🌐', '💼', '📊', '📈', '📚', '🔤', '🔢', '🏥', '✈️', '🍳', '🧑', '😊', '😎', '🤓', '🦸', '🧙', '🥷'].map(a =>
`<option value="${a}" ${a === agent.avatar ? 'selected' : ''}>${a}</option>`
).join('')}
</select>
</div>
<div class="form-group">
<label class="form-label">类别</label>
<select class="form-select" id="agentCategory">
<option value="basic" ${agent.category === 'basic' ? 'selected' : ''}>基础</option>
<option value="work" ${agent.category === 'work' ? 'selected' : ''}>工作</option>
<option value="study" ${agent.category === 'study' ? 'selected' : ''}>学习</option>
<option value="life" ${agent.category === 'life' ? 'selected' : ''}>生活</option>
</select>
</div>
<div class="form-group">
<label class="form-label">标签逗号分隔</label>
<input type="text" class="form-input" id="agentTags" value="${agent.tags || ''}">
<span style="color: #999; font-size: 12px;">可选: hot(热门), popular(推荐), new(新品)</span>
</div>
<div class="form-group">
<label class="form-label">描述</label>
<input type="text" class="form-input" id="agentDescription" value="${agent.description || ''}">
</div>
<div class="form-group">
<label class="form-label">System Prompt</label>
<textarea class="form-textarea" id="agentPrompt">${agent.system_prompt || ''}</textarea>
</div>
<div class="form-group">
<label class="form-label">LLM配置可选</label>
<select class="form-select" id="agentLLM">
<option value="" ${!agent.llm_config_id ? 'selected' : ''}>使用默认</option>
${llmConfigs.map(c => `<option value="${c.id}" ${c.id === agent.llm_config_id ? 'selected' : ''}>${c.name}</option>`).join('')}
</select>
</div>
<div class="form-group">
<label class="form-label">可使用工具逗号分隔</label>
<input type="text" class="form-input" id="agentEnableTools" value="${agent.enable_tools || ''}">
<span style="color: #999; font-size: 12px;">已配置工具: ${toolConfigs.map(t => t.tool_id).join(', ')}</span>
</div>
<div class="form-group">
<label class="form-label">热度</label>
<input type="number" class="form-input" id="agentHeat" value="${agent.heat || 0}">
</div>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="agentIsOnline" ${agent.is_online ? 'checked' : ''}> 已上线
</label>
</div>
<button class="form-submit" onclick="updateAgent('${agentId}')">保存</button>
`);
}
async function saveAgent() {
const data = {
agent_id: document.getElementById('agentId').value,
name: document.getElementById('agentName').value,
avatar: document.getElementById('agentAvatar').value,
category: document.getElementById('agentCategory').value,
tags: document.getElementById('agentTags').value,
description: document.getElementById('agentDescription').value,
system_prompt: document.getElementById('agentPrompt').value,
llm_config_id: document.getElementById('agentLLM').value || null,
enable_tools: document.getElementById('agentEnableTools').value,
heat: parseInt(document.getElementById('agentHeat').value),
is_online: document.getElementById('agentIsOnline').checked ? 1 : 0
};
if (!data.agent_id || !data.name || !data.system_prompt) {
showToast('请填写完整信息');
return;
}
await fetchAPI('/api/admin/agents', 'POST', data);
closeModal();
showToast('添加成功');
loadPage('agents');
}
async function updateAgent(agentId) {
const data = {
name: document.getElementById('agentName').value,
avatar: document.getElementById('agentAvatar').value,
category: document.getElementById('agentCategory').value,
tags: document.getElementById('agentTags').value,
description: document.getElementById('agentDescription').value,
system_prompt: document.getElementById('agentPrompt').value,
llm_config_id: document.getElementById('agentLLM').value || null,
enable_tools: document.getElementById('agentEnableTools').value,
heat: parseInt(document.getElementById('agentHeat').value),
is_online: document.getElementById('agentIsOnline').checked ? 1 : 0
};
await fetchAPI(`/api/admin/agents/${agentId}`, 'PUT', data);
closeModal();
showToast('更新成功');
loadPage('agents');
}
async function deleteAgent(agentId) {
if (!confirm('确定删除此智能体?')) return;
await fetchAPI(`/api/admin/agents/${agentId}`, 'DELETE');
showToast('删除成功');
loadPage('agents');
}
// ==================== 对话配置页面 ====================
async function loadChatConfigPage(content) {
llmConfigs = await fetchAPI('/api/admin/llm');
toolConfigs = await fetchAPI('/api/admin/tools');
const chatConfig = await fetchAPI('/api/admin/chat');
content.innerHTML = `
<div class="content-header">
<h1 class="content-title">对话配置</h1>
</div>
<div style="background: white; padding: 24px; border-radius: 12px;">
<p style="color: #718096; margin-bottom: 24px;">
此配置对所有用户的普通对话生效修改后立即生效
</p>
<div class="form-group">
<label class="form-label">LLM 配置对话使用的大模型</label>
<select class="form-select" id="chatLLMConfig">
${llmConfigs.map(l => `<option value="${l.id}" ${l.id === chatConfig.llm_config_id ? 'selected' : ''}>${l.name}</option>`).join('')}
</select>
</div>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="chatEnableSearch" ${chatConfig.enable_search ? 'checked' : ''} onchange="toggleEnableSearch()">
启用联网搜索
</label>
<span style="color: #999; font-size: 12px; margin-top: 4px;">开启后对话可联网搜索实时信息</span>
</div>
<div class="form-group">
<label class="form-label">可用工具逗号分隔</label>
<input type="text" class="form-input" id="chatEnableTools" value="${chatConfig.enable_tools || ''}" placeholder="如search,calculator">
<span style="color: #999; font-size: 12px;">已配置工具: ${toolConfigs.map(t => t.tool_id).join(', ')}</span>
</div>
<div class="form-group">
<label class="form-label">历史记录数对话上下文</label>
<input type="number" class="form-input" id="chatMaxHistory" value="${chatConfig.max_history || 20}">
<span style="color: #999; font-size: 12px;">发送给模型的对话历史条数</span>
</div>
<div class="form-group">
<label class="form-label">Temperature创造性</label>
<input type="number" class="form-input" id="chatTemperature" value="${chatConfig.temperature || 0.7}" step="0.1" min="0" max="2">
<span style="color: #999; font-size: 12px;">0-2之间越高越随机创造性越低越稳定精确</span>
</div>
<div class="form-group">
<label class="form-label">系统提示词可选</label>
<textarea class="form-textarea" id="chatSystemPrompt" placeholder="全局系统提示词,对所有对话生效">${chatConfig.system_prompt || ''}</textarea>
</div>
<button class="form-submit" onclick="saveChatConfig()">保存配置</button>
</div>
<div style="margin-top: 24px; padding: 16px; background: white; border-radius: 12px;">
<h3 style="margin-bottom: 16px;">参数说明</h3>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
<div style="padding: 12px; background: #f5f7fa; border-radius: 8px;">
<strong>🧠 LLM配置</strong>
<p style="color: #718096; font-size: 12px; margin-top: 4px;">选择对话使用的大模型接口</p>
</div>
<div style="padding: 12px; background: #f5f7fa; border-radius: 8px;">
<strong>🔍 启用联网搜索</strong>
<p style="color: #718096; font-size: 12px; margin-top: 4px;">用户可在对话中开启联网搜索</p>
</div>
<div style="padding: 12px; background: #f5f7fa; border-radius: 8px;">
<strong>🔧 可用工具</strong>
<p style="color: #718096; font-size: 12px; margin-top: 4px;">对话中可使用的工具列表</p>
</div>
<div style="padding: 12px; background: #f5f7fa; border-radius: 8px;">
<strong>📜 历史记录数</strong>
<p style="color: #718096; font-size: 12px; margin-top: 4px;">发送多少条历史对话给模型</p>
</div>
</div>
</div>
`;
}
function toggleEnableSearch() {
const enableSearch = document.getElementById('chatEnableSearch').checked;
const toolsInput = document.getElementById('chatEnableTools');
// 自动添加/移除 search 工具
let tools = toolsInput.value.split(',').filter(t => t.trim());
if (enableSearch) {
if (!tools.includes('search')) {
tools.push('search');
}
} else {
tools = tools.filter(t => t !== 'search');
}
toolsInput.value = tools.join(',');
}
async function saveChatConfig() {
const data = {
llm_config_id: parseInt(document.getElementById('chatLLMConfig').value),
enable_search: document.getElementById('chatEnableSearch').checked ? 1 : 0,
enable_tools: document.getElementById('chatEnableTools').value,
max_history: parseInt(document.getElementById('chatMaxHistory').value),
temperature: parseFloat(document.getElementById('chatTemperature').value),
system_prompt: document.getElementById('chatSystemPrompt').value
};
await fetchAPI('/api/admin/chat', 'PUT', data);
showToast('保存成功');
loadPage('chat');
}
// ==================== 工具配置页面 ====================
async function loadToolsPage(content) {
toolConfigs = await fetchAPI('/api/admin/tools');
const toolTypes = {
search: '搜索工具',
calculator: '计算器',
image: '图像生成',
code: '代码执行',
weather: '天气查询',
custom: '自定义'
};
content.innerHTML = `
<div class="content-header">
<h1 class="content-title">工具配置</h1>
<button class="add-btn" onclick="showAddToolModal()">+ 添加工具</button>
</div>
<div class="data-table">
<table>
<thead>
<tr>
<th>工具ID</th>
<th>名称</th>
<th>类型</th>
<th>提供商</th>
<th>API URL</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${toolConfigs.map(t => `
<tr>
<td>${t.tool_id} ${t.is_default ? '<span class="default-badge">默认</span>' : ''}</td>
<td>${t.name}</td>
<td>${toolTypes[t.type] || t.type}</td>
<td>${t.provider}</td>
<td style="max-width: 200px; overflow: hidden; text-overflow: ellipsis;">${t.api_url}</td>
<td>${t.is_active ? '✅ 启用' : '❌ 禁用'}</td>
<td>
<div class="action-btns">
<button class="action-btn edit" onclick="showEditToolModal('${t.tool_id}')">编辑</button>
${!t.is_default ? `<button class="action-btn default" onclick="setDefaultTool('${t.tool_id}')">设为默认</button>` : ''}
<button class="action-btn delete" onclick="deleteTool('${t.tool_id}')">删除</button>
</div>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
<div style="margin-top: 24px; padding: 16px; background: white; border-radius: 12px;">
<h3 style="margin-bottom: 16px;">工具类型说明</h3>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
<div style="padding: 12px; background: #f5f7fa; border-radius: 8px;">
<strong>🔍 search</strong>
<p style="color: #718096; font-size: 12px; margin-top: 4px;">联网搜索工具TavilyGoogle等</p>
</div>
<div style="padding: 12px; background: #f5f7fa; border-radius: 8px;">
<strong>🧮 calculator</strong>
<p style="color: #718096; font-size: 12px; margin-top: 4px;">数学计算工具</p>
</div>
<div style="padding: 12px; background: #f5f7fa; border-radius: 8px;">
<strong>🎨 image</strong>
<p style="color: #718096; font-size: 12px; margin-top: 4px;">图像生成工具DALL-E等</p>
</div>
<div style="padding: 12px; background: #f5f7fa; border-radius: 8px;">
<strong>💻 code</strong>
<p style="color: #718096; font-size: 12px; margin-top: 4px;">代码执行工具</p>
</div>
</div>
</div>
`;
}
function showAddToolModal() {
showModal('添加工具', `
<div class="form-group">
<label class="form-label">工具ID</label>
<input type="text" class="form-input" id="toolId" placeholder="如search">
</div>
<div class="form-group">
<label class="form-label">名称</label>
<input type="text" class="form-input" id="toolName" placeholder="如:联网搜索">
</div>
<div class="form-group">
<label class="form-label">类型</label>
<select class="form-select" id="toolType">
<option value="search">搜索工具</option>
<option value="calculator">计算器</option>
<option value="image">图像生成</option>
<option value="code">代码执行</option>
<option value="weather">天气查询</option>
<option value="custom">自定义</option>
</select>
</div>
<div class="form-group">
<label class="form-label">提供商</label>
<input type="text" class="form-input" id="toolProvider" placeholder="如tavily">
</div>
<div class="form-group">
<label class="form-label">API URL</label>
<input type="text" class="form-input" id="toolApiUrl" placeholder="API endpoint URL">
</div>
<div class="form-group">
<label class="form-label">API Key</label>
<input type="text" class="form-input" id="toolApiKey" placeholder="API Key可选">
</div>
<div class="form-group">
<label class="form-label">最大结果数</label>
<input type="number" class="form-input" id="toolMaxResults" value="10">
</div>
<div class="form-group">
<label class="form-label">额外配置JSON格式</label>
<textarea class="form-textarea" id="toolConfigJson" placeholder="{}"></textarea>
</div>
<button class="form-submit" onclick="saveTool()">保存</button>
`);
}
function showEditToolModal(toolId) {
const tool = toolConfigs.find(t => t.tool_id === toolId);
if (!tool) return;
const toolTypes = ['search', 'calculator', 'image', 'code', 'weather', 'custom'];
showModal('编辑工具', `
<div class="form-group">
<label class="form-label">工具ID不可修改</label>
<input type="text" class="form-input" id="toolId" value="${tool.tool_id}" readonly style="background: #f5f7fa;">
</div>
<div class="form-group">
<label class="form-label">名称</label>
<input type="text" class="form-input" id="toolName" value="${tool.name}">
</div>
<div class="form-group">
<label class="form-label">类型</label>
<select class="form-select" id="toolType">
${toolTypes.map(t => `<option value="${t}" ${t === tool.type ? 'selected' : ''}>${t}</option>`).join('')}
</select>
</div>
<div class="form-group">
<label class="form-label">提供商</label>
<input type="text" class="form-input" id="toolProvider" value="${tool.provider}">
</div>
<div class="form-group">
<label class="form-label">API URL</label>
<input type="text" class="form-input" id="toolApiUrl" value="${tool.api_url}">
</div>
<div class="form-group">
<label class="form-label">API Key</label>
<input type="text" class="form-input" id="toolApiKey" value="${tool.api_key || ''}">
</div>
<div class="form-group">
<label class="form-label">最大结果数</label>
<input type="number" class="form-input" id="toolMaxResults" value="${tool.max_results || 10}">
</div>
<div class="form-group">
<label class="form-label">额外配置JSON格式</label>
<textarea class="form-textarea" id="toolConfigJson">${tool.config_json || '{}'}</textarea>
</div>
<button class="form-submit" onclick="updateTool('${toolId}')">保存</button>
`);
}
async function saveTool() {
const data = {
tool_id: document.getElementById('toolId').value,
name: document.getElementById('toolName').value,
type: document.getElementById('toolType').value,
provider: document.getElementById('toolProvider').value,
api_url: document.getElementById('toolApiUrl').value,
api_key: document.getElementById('toolApiKey').value,
max_results: parseInt(document.getElementById('toolMaxResults').value),
config_json: document.getElementById('toolConfigJson').value
};
if (!data.tool_id || !data.name || !data.type) {
showToast('请填写完整信息');
return;
}
await fetchAPI('/api/admin/tools', 'POST', data);
closeModal();
showToast('添加成功');
loadPage('tools');
}
async function updateTool(toolId) {
const data = {
name: document.getElementById('toolName').value,
type: document.getElementById('toolType').value,
provider: document.getElementById('toolProvider').value,
api_url: document.getElementById('toolApiUrl').value,
api_key: document.getElementById('toolApiKey').value,
max_results: parseInt(document.getElementById('toolMaxResults').value),
config_json: document.getElementById('toolConfigJson').value
};
await fetchAPI(`/api/admin/tools/${toolId}`, 'PUT', data);
closeModal();
showToast('更新成功');
loadPage('tools');
}
async function setDefaultTool(toolId) {
await fetchAPI(`/api/admin/tools/${toolId}/default`, 'POST');
showToast('已设为默认');
loadPage('tools');
}
async function deleteTool(toolId) {
if (!confirm('确定删除此工具?')) return;
await fetchAPI(`/api/admin/tools/${toolId}`, 'DELETE');
showToast('删除成功');
loadPage('tools');
}
// ==================== 系统设置页面 ====================
async function loadSystemPage(content) {
systemConfigs = await fetchAPI('/api/admin/system');
content.innerHTML = `
<div class="content-header">
<h1 class="content-title">系统设置</h1>
</div>
<div class="data-table" style="padding: 24px;">
<div class="form-group">
<label class="form-label">应用名称</label>
<input type="text" class="form-input" id="appName" value="${systemConfigs.app_name?.value || ''}">
<span style="color: #999; font-size: 12px;">${systemConfigs.app_name?.description || ''}</span>
</div>
<div class="form-group">
<label class="form-label">应用版本</label>
<input type="text" class="form-input" id="appVersion" value="${systemConfigs.app_version?.value || ''}">
</div>
<h3 style="margin: 24px 0 16px; padding-top: 16px; border-top: 1px solid #e2e8f0;">游客使用限制</h3>
<div class="form-group">
<label class="form-label">每日对话会话数</label>
<input type="number" class="form-input" id="guestChatSessions" value="${systemConfigs.guest_chat_sessions?.value || 1}">
</div>
<div class="form-group">
<label class="form-label">每日对话消息数</label>
<input type="number" class="form-input" id="guestChatMessages" value="${systemConfigs.guest_chat_messages?.value || 20}">
</div>
<div class="form-group">
<label class="form-label">每日智能体消息数</label>
<input type="number" class="form-input" id="guestAgentMessages" value="${systemConfigs.guest_agent_messages?.value || 20}">
</div>
<h3 style="margin: 24px 0 16px; padding-top: 16px; border-top: 1px solid #e2e8f0;">管理员设置</h3>
<div class="form-group">
<label class="form-label">管理员密码</label>
<input type="text" class="form-input" id="adminPassword" value="${systemConfigs.admin_password?.value || ''}" placeholder="修改管理员密码">
</div>
<button class="form-submit" onclick="saveSystemConfig()">保存设置</button>
</div>
<div style="margin-top: 16px; padding: 12px; background: #f5f7fa; border-radius: 8px;">
<p style="color: #718096; font-size: 13px;">
💡 提示启用联网搜索等对话相关配置请前往"对话配置"页面设置
</p>
</div>
`;
}
async function saveSystemConfig() {
const data = {
app_name: document.getElementById('appName').value,
app_version: document.getElementById('appVersion').value,
guest_chat_sessions: document.getElementById('guestChatSessions').value,
guest_chat_messages: document.getElementById('guestChatMessages').value,
guest_agent_messages: document.getElementById('guestAgentMessages').value,
admin_password: document.getElementById('adminPassword').value
};
await fetchAPI('/api/admin/system', 'POST', data);
showToast('保存成功');
}
// ==================== 工具函数 ====================
async function fetchAPI(url, method = 'GET', data = null) {
const options = {
method,
headers: { 'Content-Type': 'application/json' }
};
if (data) {
options.body = JSON.stringify(data);
}
const res = await fetch(`${API_BASE}${url}`, options);
return res.json();
}
function showModal(title, content) {
const modal = document.getElementById('modal');
const modalContent = document.getElementById('modalContent');
modalContent.innerHTML = `
<div class="modal-header">
<h2 class="modal-title">${title}</h2>
<span class="modal-close" onclick="closeModal()">×</span>
</div>
${content}
`;
modal.classList.add('show');
}
function closeModal() {
document.getElementById('modal').classList.remove('show');
}
function showToast(message) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 2000);
}
// 点击模态框背景关闭
document.getElementById('modal').addEventListener('click', (e) => {
if (e.target.id === 'modal') {
closeModal();
}
});