新增对话配置页面: - 配置普通对话使用的LLM、可用工具 - 历史记录数、Temperature、系统提示词 - 支持多配置、设为默认 搜索配置改为工具配置: - 工具类型: search/calculator/image/code/weather/custom - 搜索作为工具之一管理 - 支持添加多种工具 - 工具配置: ID、名称、类型、提供商、API、Key、额外配置 API新增: - /api/admin/chat - 对话配置CRUD - /api/admin/tools - 工具配置CRUD - /api/config 返回 tools 和 chat_config
1118 lines
41 KiB
JavaScript
1118 lines
41 KiB
JavaScript
// 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');
|
||
|
||
const categories = {
|
||
hot: '热门',
|
||
work: '工作',
|
||
study: '学习',
|
||
life: '生活'
|
||
};
|
||
|
||
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 style="max-width: 200px; overflow: hidden; text-overflow: ellipsis;">${a.description || '-'}</td>
|
||
<td>${a.heat || 0}</td>
|
||
<td>${a.is_active ? '✅ 启用' : '❌ 禁用'}</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>
|
||
`;
|
||
}
|
||
|
||
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="hot">热门</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="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="hot" ${agent.category === 'hot' ? '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="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="">使用默认</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="number" class="form-input" id="agentHeat" value="${agent.heat || 0}">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">标签</label>
|
||
<input type="text" class="form-input" id="agentTags" value="${agent.tags || ''}">
|
||
</div>
|
||
<div class="form-group">
|
||
<label style="display: flex; align-items: center; gap: 8px;">
|
||
<input type="checkbox" id="agentEnableSearch" ${agent.enable_search ? '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,
|
||
description: document.getElementById('agentDescription').value,
|
||
system_prompt: document.getElementById('agentPrompt').value,
|
||
llm_config_id: document.getElementById('agentLLM').value || null,
|
||
heat: parseInt(document.getElementById('agentHeat').value),
|
||
tags: document.getElementById('agentTags').value,
|
||
enable_search: document.getElementById('agentEnableSearch').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,
|
||
description: document.getElementById('agentDescription').value,
|
||
system_prompt: document.getElementById('agentPrompt').value,
|
||
llm_config_id: document.getElementById('agentLLM').value || null,
|
||
heat: parseInt(document.getElementById('agentHeat').value),
|
||
tags: document.getElementById('agentTags').value,
|
||
enable_search: document.getElementById('agentEnableSearch').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) {
|
||
chatConfigs = await fetchAPI('/api/admin/chat');
|
||
llmConfigs = await fetchAPI('/api/admin/llm');
|
||
toolConfigs = await fetchAPI('/api/admin/tools');
|
||
|
||
content.innerHTML = `
|
||
<div class="content-header">
|
||
<h1 class="content-title">对话配置</h1>
|
||
<button class="add-btn" onclick="showAddChatConfigModal()">+ 添加配置</button>
|
||
</div>
|
||
|
||
<div class="data-table">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>配置名称</th>
|
||
<th>LLM</th>
|
||
<th>可用工具</th>
|
||
<th>历史记录数</th>
|
||
<th>Temperature</th>
|
||
<th>状态</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${chatConfigs.map(c => `
|
||
<tr>
|
||
<td>${c.name} ${c.is_default ? '<span class="default-badge">默认</span>' : ''}</td>
|
||
<td>${llmConfigs.find(l => l.id === c.llm_config_id)?.name || '未指定'}</td>
|
||
<td>${c.enable_tools || '无'}</td>
|
||
<td>${c.max_history || 20}</td>
|
||
<td>${c.temperature || 0.7}</td>
|
||
<td>${c.is_default ? '✅ 默认' : '-'}</td>
|
||
<td>
|
||
<div class="action-btns">
|
||
<button class="action-btn edit" onclick="showEditChatConfigModal('${c.config_id}')">编辑</button>
|
||
${!c.is_default ? `<button class="action-btn default" onclick="setDefaultChatConfig('${c.config_id}')">设为默认</button>` : ''}
|
||
<button class="action-btn delete" onclick="deleteChatConfig('${c.config_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>
|
||
<p style="color: #718096; font-size: 14px;">
|
||
对话配置用于控制普通对话的各项参数。可以选择使用的大模型接口、启用的工具、历史记录数量等。
|
||
前端 APP 会自动使用默认配置进行对话。
|
||
</p>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function showAddChatConfigModal() {
|
||
showModal('添加对话配置', `
|
||
<div class="form-group">
|
||
<label class="form-label">配置ID</label>
|
||
<input type="text" class="form-input" id="chatConfigId" placeholder="如:default">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">配置名称</label>
|
||
<input type="text" class="form-input" id="chatConfigName" placeholder="如:默认对话配置">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">LLM 配置</label>
|
||
<select class="form-select" id="chatLLMConfig">
|
||
${llmConfigs.map(l => `<option value="${l.id}">${l.name}</option>`).join('')}
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">可用工具(逗号分隔)</label>
|
||
<input type="text" class="form-input" id="chatEnableTools" 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="20">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Temperature</label>
|
||
<input type="number" class="form-input" id="chatTemperature" value="0.7" step="0.1">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">系统提示词(可选)</label>
|
||
<textarea class="form-textarea" id="chatSystemPrompt" placeholder="全局系统提示词"></textarea>
|
||
</div>
|
||
<button class="form-submit" onclick="saveChatConfig()">保存</button>
|
||
`);
|
||
}
|
||
|
||
function showEditChatConfigModal(configId) {
|
||
const config = chatConfigs.find(c => c.config_id === configId);
|
||
if (!config) return;
|
||
|
||
showModal('编辑对话配置', `
|
||
<div class="form-group">
|
||
<label class="form-label">配置ID(不可修改)</label>
|
||
<input type="text" class="form-input" id="chatConfigId" value="${config.config_id}" readonly style="background: #f5f7fa;">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">配置名称</label>
|
||
<input type="text" class="form-input" id="chatConfigName" value="${config.name}">
|
||
</div>
|
||
<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 === config.llm_config_id ? 'selected' : ''}>${l.name}</option>`).join('')}
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">可用工具(逗号分隔)</label>
|
||
<input type="text" class="form-input" id="chatEnableTools" value="${config.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="chatMaxHistory" value="${config.max_history || 20}">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Temperature</label>
|
||
<input type="number" class="form-input" id="chatTemperature" value="${config.temperature || 0.7}" step="0.1">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">系统提示词(可选)</label>
|
||
<textarea class="form-textarea" id="chatSystemPrompt">${config.system_prompt || ''}</textarea>
|
||
</div>
|
||
<button class="form-submit" onclick="updateChatConfig('${configId}')">保存</button>
|
||
`);
|
||
}
|
||
|
||
async function saveChatConfig() {
|
||
const data = {
|
||
config_id: document.getElementById('chatConfigId').value,
|
||
name: document.getElementById('chatConfigName').value,
|
||
llm_config_id: parseInt(document.getElementById('chatLLMConfig').value),
|
||
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
|
||
};
|
||
|
||
if (!data.config_id || !data.name) {
|
||
showToast('请填写完整信息');
|
||
return;
|
||
}
|
||
|
||
await fetchAPI('/api/admin/chat', 'POST', data);
|
||
closeModal();
|
||
showToast('添加成功');
|
||
loadPage('chat');
|
||
}
|
||
|
||
async function updateChatConfig(configId) {
|
||
const data = {
|
||
name: document.getElementById('chatConfigName').value,
|
||
llm_config_id: parseInt(document.getElementById('chatLLMConfig').value),
|
||
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/${configId}`, 'PUT', data);
|
||
closeModal();
|
||
showToast('更新成功');
|
||
loadPage('chat');
|
||
}
|
||
|
||
async function setDefaultChatConfig(configId) {
|
||
await fetchAPI(`/api/admin/chat/${configId}/default`, 'POST');
|
||
showToast('已设为默认');
|
||
loadPage('chat');
|
||
}
|
||
|
||
async function deleteChatConfig(configId) {
|
||
if (!confirm('确定删除此配置?')) return;
|
||
await fetchAPI(`/api/admin/chat/${configId}`, 'DELETE');
|
||
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;">联网搜索工具(Tavily、Google等)</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>
|
||
|
||
<div class="form-group">
|
||
<label style="display: flex; align-items: center; gap: 8px;">
|
||
<input type="checkbox" id="enableSearch" ${systemConfigs.enable_search?.value === 'true' ? 'checked' : ''}>
|
||
启用联网搜索
|
||
</label>
|
||
</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>
|
||
`;
|
||
}
|
||
|
||
async function saveSystemConfig() {
|
||
const data = {
|
||
app_name: document.getElementById('appName').value,
|
||
app_version: document.getElementById('appVersion').value,
|
||
enable_search: document.getElementById('enableSearch').checked ? 'true' : 'false',
|
||
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('保存成功');
|
||
loadPage('system');
|
||
}
|
||
|
||
// ==================== 工具函数 ====================
|
||
|
||
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();
|
||
}
|
||
}); |