feat: 后台增加对话配置+工具配置管理

新增对话配置页面:
- 配置普通对话使用的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
This commit is contained in:
2026-04-27 12:51:44 +08:00
parent 2f60e30169
commit 423f3aa717
3 changed files with 484 additions and 122 deletions

View File

@@ -5,7 +5,8 @@ const API_BASE = '';
let currentPage = 'stats';
let llmConfigs = [];
let agents = [];
let searchConfigs = [];
let toolConfigs = [];
let chatConfigs = [];
let systemConfigs = {};
// ==================== 登录 ====================
@@ -86,8 +87,11 @@ async function loadPage(page) {
case 'agents':
await loadAgentsPage(content);
break;
case 'search':
await loadSearchPage(content);
case 'chat':
await loadChatConfigPage(content);
break;
case 'tools':
await loadToolsPage(content);
break;
case 'system':
await loadSystemPage(content);
@@ -579,42 +583,46 @@ async function deleteAgent(agentId) {
loadPage('agents');
}
// ==================== 搜索配置页面 ====================
// ==================== 对话配置页面 ====================
async function loadSearchPage(content) {
searchConfigs = await fetchAPI('/api/admin/search');
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="showAddSearchModal()">+ 添加配置</button>
<h1 class="content-title">对话配置</h1>
<button class="add-btn" onclick="showAddChatConfigModal()">+ 添加配置</button>
</div>
<div class="data-table">
<table>
<thead>
<tr>
<th>名称</th>
<th>提供商</th>
<th>API URL</th>
<th>最大结果数</th>
<th>配置名称</th>
<th>LLM</th>
<th>可用工具</th>
<th>历史记录数</th>
<th>Temperature</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${searchConfigs.map(c => `
${chatConfigs.map(c => `
<tr>
<td>${c.name} ${c.is_default ? '<span class="default-badge">默认</span>' : ''}</td>
<td>${c.provider}</td>
<td style="max-width: 200px; overflow: hidden; text-overflow: ellipsis;">${c.api_url}</td>
<td>${c.max_results}</td>
<td>${c.is_active ? '✅ 启用' : '❌ 禁用'}</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="showEditSearchModal(${c.id})">编辑</button>
${!c.is_default ? `<button class="action-btn default" onclick="setDefaultSearch(${c.id})">设为默认</button>` : ''}
<button class="action-btn delete" onclick="deleteSearch(${c.id})">删除</button>
<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>
@@ -622,120 +630,365 @@ async function loadSearchPage(content) {
</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 showAddSearchModal() {
showModal('添加搜索配置', `
function showAddChatConfigModal() {
showModal('添加对话配置', `
<div class="form-group">
<label class="form-label">名称</label>
<input type="text" class="form-input" id="searchName" placeholder="如:Tavily">
<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>
<select class="form-select" id="searchProvider">
<option value="tavily">Tavily</option>
<option value="google">Google</option>
<option value="bing">Bing</option>
<option value="custom">自定义</option>
<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">API URL</label>
<input type="text" class="form-input" id="searchApiUrl" placeholder="API endpoint URL">
<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">API Key</label>
<input type="text" class="form-input" id="searchApiKey" placeholder="API Key">
<label class="form-label">历史记录数</label>
<input type="number" class="form-input" id="chatMaxHistory" value="20">
</div>
<div class="form-group">
<label class="form-label">最大结果数</label>
<input type="number" class="form-input" id="searchMaxResults" value="10">
<label class="form-label">Temperature</label>
<input type="number" class="form-input" id="chatTemperature" value="0.7" step="0.1">
</div>
<button class="form-submit" onclick="saveSearch()">保存</button>
<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 showEditSearchModal(id) {
const config = searchConfigs.find(c => c.id === id);
function showEditChatConfigModal(configId) {
const config = chatConfigs.find(c => c.config_id === configId);
if (!config) return;
showModal('编辑搜索配置', `
showModal('编辑对话配置', `
<div class="form-group">
<label class="form-label">名称</label>
<input type="text" class="form-input" id="searchName" value="${config.name}">
<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>
<select class="form-select" id="searchProvider">
<option value="tavily" ${config.provider === 'tavily' ? 'selected' : ''}>Tavily</option>
<option value="google" ${config.provider === 'google' ? 'selected' : ''}>Google</option>
<option value="bing" ${config.provider === 'bing' ? 'selected' : ''}>Bing</option>
<option value="custom" ${config.provider === 'custom' ? 'selected' : ''}>自定义</option>
<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">API URL</label>
<input type="text" class="form-input" id="searchApiUrl" value="${config.api_url}">
<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">API Key</label>
<input type="text" class="form-input" id="searchApiKey" value="${config.api_key}">
<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">最大结果数</label>
<input type="number" class="form-input" id="searchMaxResults" value="${config.max_results}">
<label class="form-label">Temperature</label>
<input type="number" class="form-input" id="chatTemperature" value="${config.temperature || 0.7}" step="0.1">
</div>
<button class="form-submit" onclick="updateSearch(${id})">保存</button>
<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 saveSearch() {
async function saveChatConfig() {
const data = {
name: document.getElementById('searchName').value,
provider: document.getElementById('searchProvider').value,
api_url: document.getElementById('searchApiUrl').value,
api_key: document.getElementById('searchApiKey').value,
max_results: parseInt(document.getElementById('searchMaxResults').value)
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.name || !data.api_url || !data.api_key) {
if (!data.config_id || !data.name) {
showToast('请填写完整信息');
return;
}
await fetchAPI('/api/admin/search', 'POST', data);
await fetchAPI('/api/admin/chat', 'POST', data);
closeModal();
showToast('添加成功');
loadPage('search');
loadPage('chat');
}
async function updateSearch(id) {
async function updateChatConfig(configId) {
const data = {
name: document.getElementById('searchName').value,
provider: document.getElementById('searchProvider').value,
api_url: document.getElementById('searchApiUrl').value,
api_key: document.getElementById('searchApiKey').value,
max_results: parseInt(document.getElementById('searchMaxResults').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
};
await fetchAPI(`/api/admin/search/${id}`, 'PUT', data);
await fetchAPI(`/api/admin/chat/${configId}`, 'PUT', data);
closeModal();
showToast('更新成功');
loadPage('search');
loadPage('chat');
}
async function setDefaultSearch(id) {
await fetchAPI(`/api/admin/search/${id}/default`, 'POST');
async function setDefaultChatConfig(configId) {
await fetchAPI(`/api/admin/chat/${configId}/default`, 'POST');
showToast('已设为默认');
loadPage('search');
loadPage('chat');
}
async function deleteSearch(id) {
async function deleteChatConfig(configId) {
if (!confirm('确定删除此配置?')) return;
await fetchAPI(`/api/admin/search/${id}`, 'DELETE');
await fetchAPI(`/api/admin/chat/${configId}`, 'DELETE');
showToast('删除成功');
loadPage('search');
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');
}
// ==================== 系统设置页面 ====================