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 切换上线状态
- 前端配置只返回上线且活跃的智能体
This commit is contained in:
2026-04-27 14:15:14 +08:00
parent c725aeb192
commit f85991064f
2 changed files with 145 additions and 41 deletions

View File

@@ -66,9 +66,10 @@ def init_db():
llm_config_id INTEGER, llm_config_id INTEGER,
temperature REAL DEFAULT 0.7, temperature REAL DEFAULT 0.7,
max_tokens INTEGER DEFAULT 2048, max_tokens INTEGER DEFAULT 2048,
enable_search INTEGER DEFAULT 0, enable_tools TEXT,
tags TEXT, tags TEXT,
heat INTEGER DEFAULT 0, heat INTEGER DEFAULT 0,
is_online INTEGER DEFAULT 1,
is_active INTEGER DEFAULT 1, is_active INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
@@ -176,24 +177,28 @@ def init_db():
cursor.execute('SELECT COUNT(*) FROM agents') cursor.execute('SELECT COUNT(*) FROM agents')
if cursor.fetchone()[0] == 0: if cursor.fetchone()[0] == 0:
default_agents = [ default_agents = [
('assistant', '通用助手', '🤖', 'hot', '能回答各类问题,帮助写作、分析、解答疑惑', '你是一个智能助手,能够回答各类问题,帮助用户解决问题。', 9500), # 基础类别
('writer', '写作助手', '✍️', 'hot', '专注于文章写作、文案创作、内容润色', '你是一个专业的写作助手,擅长各类文章写作、文案创作和内容润色。', 8800), ('assistant', '通用助手', '🤖', 'basic', '能回答各类问题,帮助写作、分析、解答疑惑', '你是一个智能助手,能够回答各类问题,帮助用户解决问题。', 'hot', 9500),
('coder', '编程助手', '👨‍💻', 'hot', '精通编程语言,解答技术问题,生成代码', '你是一个专业的编程助手,精通各类编程语言,能够解答技术问题并生成高质量代码。', 8500), ('writer', '写作助手', '✍️', 'basic', '专注于文章写作、文案创作、内容润色', '你是一个专业的写作助手,擅长各类文章写作、文案创作和内容润色。', 'hot', 8800),
('translator', '翻译助手', '🌐', 'hot', '多语言翻译,精准表达,文化适配', '你是一个专业的翻译助手,精通多语言翻译,能够精准表达并适配文化差异。', 7200), ('coder', '编程助手', '👨‍💻', 'basic', '精通编程语言,解答技术问题,生成代码', '你是一个专业的编程助手,精通各类编程语言,能够解答技术问题并生成高质量代码。', 'hot', 8500),
('work', '工作助手', '💼', 'work', '职场问题解答,工作效率提升', '你是一个工作助手,帮助解决职场问题,提升工作效率。', 5000), ('translator', '翻译助手', '🌐', 'basic', '多语言翻译,精准表达,文化适配', '你是一个专业的翻译助手,精通多语言翻译,能够精准表达并适配文化差异。', 'hot', 7200),
('ppt', 'PPT助手', '📊', 'work', 'PPT内容生成结构优化设计建议', '你是一个PPT助手擅长PPT内容生成、结构优化和设计建议。', 4500), # 工作类别
('excel', 'Excel助手', '📈', 'work', 'Excel公式、数据分析、表格优化', '你是一个Excel助手精通Excel公式、数据分析和表格优化。', 4000), ('work', '工作助手', '💼', 'work', '职场问题解答,工作效率提升', '你是一个工作助手,帮助解决职场问题,提升工作效率。', 'popular', 5000),
('teacher', '学习助手', '📚', 'study', '知识讲解,学习方法,考试辅导', '你是一个学习助手,擅长知识讲解、学习方法指导和考试辅导。', 5500), ('ppt', 'PPT助手', '📊', 'work', 'PPT内容生成结构优化设计建议', '你是一个PPT助手,擅长PPT内容生成、结构优化和设计建议。', 'popular', 4500),
('english', '英语助手', '🔤', 'study', '英语学习,语法纠正,口语练习', '你是一个英语助手,帮助英语学习、语法纠正和口语练习。', 5000), ('excel', 'Excel助手', '📈', 'work', 'Excel公式、数据分析、表格优化', '你是一个Excel助手精通Excel公式、数据分析和表格优化。', '', 4000),
('math', '数学助手', '🔢', 'study', '数学解题,公式推导,概念讲解', '你是一个数学助手,擅长数学解题、公式推导和概念讲解。', 4500), # 学习类别
('health', '健康助手', '🏥', 'life', '健康咨询,养生建议,运动指', '你是一个健康助手,提供健康咨询、养生建议和运动指导。', 3500), ('teacher', '学习助手', '📚', 'study', '知识讲解,学习方法,考试辅', '你是一个学习助手,擅长知识讲解、学习方法指导和考试辅导。', 'popular', 5500),
('travel', '旅行助手', '✈️', 'life', '旅行规划,景点推荐,美食指南', '你是一个旅行助手,擅长旅行规划、景点推荐和美食指南。', 3200), ('english', '英语助手', '🔤', 'study', '英语学习,语法纠正,口语练习', '你是一个英语助手,帮助英语学习、语法纠正和口语练习。', '', 5000),
('food', '美食助手', '🍳', 'life', '菜谱推荐,烹饪技巧,营养搭配', '你是一个美食助手,提供菜谱推荐、烹饪技巧和营养搭配建议。', 3000), ('math', '数学助手', '🔢', 'study', '数学解题,公式推导,概念讲解', '你是一个数学助手,擅长数学解题、公式推导和概念讲解。', '', 4500),
# 生活类别
('health', '健康助手', '🏥', 'life', '健康咨询,养生建议,运动指导', '你是一个健康助手,提供健康咨询、养生建议和运动指导。', '', 3500),
('travel', '旅行助手', '✈️', 'life', '旅行规划,景点推荐,美食指南', '你是一个旅行助手,擅长旅行规划、景点推荐和美食指南。', 'popular', 3200),
('food', '美食助手', '🍳', 'life', '菜谱推荐,烹饪技巧,营养搭配', '你是一个美食助手,提供菜谱推荐、烹饪技巧和营养搭配建议。', '', 3000),
] ]
for agent in default_agents: for agent in default_agents:
cursor.execute(''' cursor.execute('''
INSERT INTO agents (agent_id, name, avatar, category, description, system_prompt, heat) INSERT INTO agents (agent_id, name, avatar, category, description, system_prompt, tags, heat, is_online)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1)
''', agent) ''', agent)
# 初始化系统配置 # 初始化系统配置
@@ -380,12 +385,13 @@ def add_agent():
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(''' cursor.execute('''
INSERT INTO agents (agent_id, name, avatar, category, description, system_prompt, INSERT INTO agents (agent_id, name, avatar, category, description, system_prompt,
llm_config_id, temperature, max_tokens, enable_search, tags, heat) llm_config_id, temperature, max_tokens, enable_tools, tags, heat, is_online)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (data['agent_id'], data['name'], data.get('avatar', '🤖'), data['category'], ''', (data['agent_id'], data['name'], data.get('avatar', '🤖'), data['category'],
data.get('description', ''), data['system_prompt'], data.get('llm_config_id'), data.get('description', ''), data['system_prompt'], data.get('llm_config_id'),
data.get('temperature', 0.7), data.get('max_tokens', 2048), data.get('temperature', 0.7), data.get('max_tokens', 2048),
data.get('enable_search', 0), data.get('tags', ''), data.get('heat', 0))) data.get('enable_tools', ''), data.get('tags', ''), data.get('heat', 0),
data.get('is_online', 1)))
conn.commit() conn.commit()
conn.close() conn.close()
return jsonify({'success': True}) return jsonify({'success': True})
@@ -399,12 +405,13 @@ def update_agent(agent_id):
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(''' cursor.execute('''
UPDATE agents SET name=?, avatar=?, category=?, description=?, system_prompt=?, UPDATE agents SET name=?, avatar=?, category=?, description=?, system_prompt=?,
llm_config_id=?, temperature=?, max_tokens=?, enable_search=?, tags=?, heat=?, llm_config_id=?, temperature=?, max_tokens=?, enable_tools=?, tags=?, heat=?, is_online=?,
updated_at=CURRENT_TIMESTAMP WHERE agent_id=? updated_at=CURRENT_TIMESTAMP WHERE agent_id=?
''', (data['name'], data.get('avatar', '🤖'), data['category'], ''', (data['name'], data.get('avatar', '🤖'), data['category'],
data.get('description', ''), data['system_prompt'], data.get('llm_config_id'), data.get('description', ''), data['system_prompt'], data.get('llm_config_id'),
data.get('temperature', 0.7), data.get('max_tokens', 2048), data.get('temperature', 0.7), data.get('max_tokens', 2048),
data.get('enable_search', 0), data.get('tags', ''), data.get('heat', 0), agent_id)) data.get('enable_tools', ''), data.get('tags', ''), data.get('heat', 0),
data.get('is_online', 1), agent_id))
conn.commit() conn.commit()
conn.close() conn.close()
return jsonify({'success': True}) return jsonify({'success': True})
@@ -421,6 +428,19 @@ def delete_agent(agent_id):
return jsonify({'success': True}) return jsonify({'success': True})
@app.route('/api/admin/agents/<agent_id>/online', methods=['POST'])
def toggle_agent_online(agent_id):
"""切换智能体上线状态"""
data = request.json
conn = get_db()
cursor = conn.cursor()
cursor.execute('UPDATE agents SET is_online=?, updated_at=CURRENT_TIMESTAMP WHERE agent_id=?',
(data.get('is_online', 1), agent_id))
conn.commit()
conn.close()
return jsonify({'success': True})
# ==================== 对话配置管理 ==================== # ==================== 对话配置管理 ====================
@app.route('/api/admin/chat', methods=['GET']) @app.route('/api/admin/chat', methods=['GET'])
@@ -643,8 +663,8 @@ def get_frontend_config():
cursor.execute('SELECT * FROM tool_configs WHERE is_default=1 AND is_active=1') cursor.execute('SELECT * FROM tool_configs WHERE is_default=1 AND is_active=1')
tools = [dict(row) for row in cursor.fetchall()] tools = [dict(row) for row in cursor.fetchall()]
# 获取所有智能体 # 获取所有智能体(上线且活跃)
cursor.execute('SELECT agent_id, name, avatar, category, description, system_prompt, heat, enable_search FROM agents WHERE is_active=1') cursor.execute('SELECT agent_id, name, avatar, category, description, system_prompt, heat, tags, enable_tools FROM agents WHERE is_online=1 AND is_active=1')
agents = [dict(row) for row in cursor.fetchall()] agents = [dict(row) for row in cursor.fetchall()]
# 获取默认对话配置 # 获取默认对话配置

View File

@@ -361,14 +361,22 @@ async function deleteLLM(id) {
async function loadAgentsPage(content) { async function loadAgentsPage(content) {
agents = await fetchAPI('/api/admin/agents'); agents = await fetchAPI('/api/admin/agents');
llmConfigs = await fetchAPI('/api/admin/llm'); llmConfigs = await fetchAPI('/api/admin/llm');
toolConfigs = await fetchAPI('/api/admin/tools');
const categories = { const categories = {
hot: '热门', basic: '基础',
work: '工作', work: '工作',
study: '学习', study: '学习',
life: '生活' life: '生活'
}; };
const tagLabels = {
'hot': '🔥 热门',
'popular': '👍 推荐',
'new': '🆕 新品',
'': ''
};
content.innerHTML = ` content.innerHTML = `
<div class="content-header"> <div class="content-header">
<h1 class="content-title">智能体管理</h1> <h1 class="content-title">智能体管理</h1>
@@ -382,9 +390,9 @@ async function loadAgentsPage(content) {
<th>头像</th> <th>头像</th>
<th>名称</th> <th>名称</th>
<th>类别</th> <th>类别</th>
<th>描述</th> <th>标签</th>
<th>热度</th> <th>热度</th>
<th>状态</th> <th>上线状态</th>
<th>操作</th> <th>操作</th>
</tr> </tr>
</thead> </thead>
@@ -394,9 +402,13 @@ async function loadAgentsPage(content) {
<td style="font-size: 24px;">${a.avatar}</td> <td style="font-size: 24px;">${a.avatar}</td>
<td>${a.name}</td> <td>${a.name}</td>
<td>${categories[a.category] || a.category}</td> <td>${categories[a.category] || a.category}</td>
<td style="max-width: 200px; overflow: hidden; text-overflow: ellipsis;">${a.description || '-'}</td> <td>${a.tags ? a.tags.split(',').map(t => tagLabels[t] || t).filter(t => t).join(' ') : '-'}</td>
<td>${a.heat || 0}</td> <td>${a.heat || 0}</td>
<td>${a.is_active ? '✅ 启用' : '❌ 禁用'}</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> <td>
<div class="action-btns"> <div class="action-btns">
<button class="action-btn edit" onclick="showEditAgentModal('${a.agent_id}')">编辑</button> <button class="action-btn edit" onclick="showEditAgentModal('${a.agent_id}')">编辑</button>
@@ -408,9 +420,34 @@ async function loadAgentsPage(content) {
</tbody> </tbody>
</table> </table>
</div> </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() { function showAddAgentModal() {
showModal('添加智能体', ` showModal('添加智能体', `
<div class="form-group"> <div class="form-group">
@@ -432,12 +469,51 @@ function showAddAgentModal() {
<div class="form-group"> <div class="form-group">
<label class="form-label">类别</label> <label class="form-label">类别</label>
<select class="form-select" id="agentCategory"> <select class="form-select" id="agentCategory">
<option value="hot">热门</option> <option value="basic">基础</option>
<option value="work">工作</option> <option value="work">工作</option>
<option value="study">学习</option> <option value="study">学习</option>
<option value="life">生活</option> <option value="life">生活</option>
</select> </select>
</div> </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"> <div class="form-group">
<label class="form-label">描述</label> <label class="form-label">描述</label>
<input type="text" class="form-input" id="agentDescription" placeholder="智能体描述"> <input type="text" class="form-input" id="agentDescription" placeholder="智能体描述">
@@ -494,12 +570,17 @@ function showEditAgentModal(agentId) {
<div class="form-group"> <div class="form-group">
<label class="form-label">类别</label> <label class="form-label">类别</label>
<select class="form-select" id="agentCategory"> <select class="form-select" id="agentCategory">
<option value="hot" ${agent.category === 'hot' ? 'selected' : ''}>热门</option> <option value="basic" ${agent.category === 'basic' ? 'selected' : ''}>基础</option>
<option value="work" ${agent.category === 'work' ? 'selected' : ''}>工作</option> <option value="work" ${agent.category === 'work' ? 'selected' : ''}>工作</option>
<option value="study" ${agent.category === 'study' ? 'selected' : ''}>学习</option> <option value="study" ${agent.category === 'study' ? 'selected' : ''}>学习</option>
<option value="life" ${agent.category === 'life' ? 'selected' : ''}>生活</option> <option value="life" ${agent.category === 'life' ? 'selected' : ''}>生活</option>
</select> </select>
</div> </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"> <div class="form-group">
<label class="form-label">描述</label> <label class="form-label">描述</label>
<input type="text" class="form-input" id="agentDescription" value="${agent.description || ''}"> <input type="text" class="form-input" id="agentDescription" value="${agent.description || ''}">
@@ -509,23 +590,24 @@ function showEditAgentModal(agentId) {
<textarea class="form-textarea" id="agentPrompt">${agent.system_prompt || ''}</textarea> <textarea class="form-textarea" id="agentPrompt">${agent.system_prompt || ''}</textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">LLM配置</label> <label class="form-label">LLM配置可选</label>
<select class="form-select" id="agentLLM"> <select class="form-select" id="agentLLM">
<option value="">使用默认</option> <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('')} ${llmConfigs.map(c => `<option value="${c.id}" ${c.id === agent.llm_config_id ? 'selected' : ''}>${c.name}</option>`).join('')}
</select> </select>
</div> </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"> <div class="form-group">
<label class="form-label">热度</label> <label class="form-label">热度</label>
<input type="number" class="form-input" id="agentHeat" value="${agent.heat || 0}"> <input type="number" class="form-input" id="agentHeat" value="${agent.heat || 0}">
</div> </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"> <div class="form-group">
<label style="display: flex; align-items: center; gap: 8px;"> <label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="agentEnableSearch" ${agent.enable_search ? 'checked' : ''}> 启用联网搜索 <input type="checkbox" id="agentIsOnline" ${agent.is_online ? 'checked' : ''}> 已上线
</label> </label>
</div> </div>
<button class="form-submit" onclick="updateAgent('${agentId}')">保存</button> <button class="form-submit" onclick="updateAgent('${agentId}')">保存</button>
@@ -538,12 +620,13 @@ async function saveAgent() {
name: document.getElementById('agentName').value, name: document.getElementById('agentName').value,
avatar: document.getElementById('agentAvatar').value, avatar: document.getElementById('agentAvatar').value,
category: document.getElementById('agentCategory').value, category: document.getElementById('agentCategory').value,
tags: document.getElementById('agentTags').value,
description: document.getElementById('agentDescription').value, description: document.getElementById('agentDescription').value,
system_prompt: document.getElementById('agentPrompt').value, system_prompt: document.getElementById('agentPrompt').value,
llm_config_id: document.getElementById('agentLLM').value || null, llm_config_id: document.getElementById('agentLLM').value || null,
enable_tools: document.getElementById('agentEnableTools').value,
heat: parseInt(document.getElementById('agentHeat').value), heat: parseInt(document.getElementById('agentHeat').value),
tags: document.getElementById('agentTags').value, is_online: document.getElementById('agentIsOnline').checked ? 1 : 0
enable_search: document.getElementById('agentEnableSearch').checked ? 1 : 0
}; };
if (!data.agent_id || !data.name || !data.system_prompt) { if (!data.agent_id || !data.name || !data.system_prompt) {
@@ -562,12 +645,13 @@ async function updateAgent(agentId) {
name: document.getElementById('agentName').value, name: document.getElementById('agentName').value,
avatar: document.getElementById('agentAvatar').value, avatar: document.getElementById('agentAvatar').value,
category: document.getElementById('agentCategory').value, category: document.getElementById('agentCategory').value,
tags: document.getElementById('agentTags').value,
description: document.getElementById('agentDescription').value, description: document.getElementById('agentDescription').value,
system_prompt: document.getElementById('agentPrompt').value, system_prompt: document.getElementById('agentPrompt').value,
llm_config_id: document.getElementById('agentLLM').value || null, llm_config_id: document.getElementById('agentLLM').value || null,
enable_tools: document.getElementById('agentEnableTools').value,
heat: parseInt(document.getElementById('agentHeat').value), heat: parseInt(document.getElementById('agentHeat').value),
tags: document.getElementById('agentTags').value, is_online: document.getElementById('agentIsOnline').checked ? 1 : 0
enable_search: document.getElementById('agentEnableSearch').checked ? 1 : 0
}; };
await fetchAPI(`/api/admin/agents/${agentId}`, 'PUT', data); await fetchAPI(`/api/admin/agents/${agentId}`, 'PUT', data);