diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c329ce7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +*.so +*.egg-info/ +dist/ +build/ +*.db +data.db +node_modules/ \ No newline at end of file diff --git a/backend/app.py b/backend/app.py new file mode 100644 index 0000000..c407863 --- /dev/null +++ b/backend/app.py @@ -0,0 +1,611 @@ +#!/usr/bin/env python3 +""" +AI Chat App - 后台管理服务 +端口: 19020 (与前端同一端口) +""" + +from flask import Flask, jsonify, request, send_from_directory +from flask_cors import CORS +import os +import json +import sqlite3 +from datetime import datetime +import hashlib + +app = Flask(__name__, static_folder='../www') +CORS(app) + +# 数据库路径 +DB_PATH = os.path.join(os.path.dirname(__file__), 'data.db') + +# 管理员账户(默认) +ADMIN_USERNAME = 'admin' +ADMIN_PASSWORD_HASH = hashlib.sha256('admin123'.encode()).hexdigest() + + +def get_db(): + """获取数据库连接""" + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + return conn + + +def init_db(): + """初始化数据库""" + conn = get_db() + cursor = conn.cursor() + + # 大模型接口配置表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS llm_configs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + provider TEXT NOT NULL, + api_url TEXT NOT NULL, + api_key TEXT NOT NULL, + model TEXT NOT NULL, + max_tokens INTEGER DEFAULT 2048, + temperature REAL DEFAULT 0.7, + is_default INTEGER DEFAULT 0, + is_active INTEGER DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # 智能体配置表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS agents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + agent_id TEXT NOT NULL UNIQUE, + name TEXT NOT NULL, + avatar TEXT DEFAULT '🤖', + category TEXT NOT NULL, + description TEXT, + system_prompt TEXT NOT NULL, + llm_config_id INTEGER, + temperature REAL DEFAULT 0.7, + max_tokens INTEGER DEFAULT 2048, + enable_search INTEGER DEFAULT 0, + tags TEXT, + heat INTEGER DEFAULT 0, + is_active INTEGER DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (llm_config_id) REFERENCES llm_configs(id) + ) + ''') + + # 搜索配置表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS search_configs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + provider TEXT NOT NULL, + api_url TEXT NOT NULL, + api_key TEXT NOT NULL, + max_results INTEGER DEFAULT 10, + is_default INTEGER DEFAULT 0, + is_active INTEGER DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # 系统配置表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS system_configs ( + key TEXT PRIMARY KEY, + value TEXT, + description TEXT, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # 统计日志表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS stats_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + log_type TEXT NOT NULL, + log_key TEXT NOT NULL, + log_value INTEGER DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # 用户表(后台管理) + cursor.execute(''' + CREATE TABLE IF NOT EXISTS admin_users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + role TEXT DEFAULT 'admin', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # 初始化默认大模型配置 + cursor.execute('SELECT COUNT(*) FROM llm_configs') + if cursor.fetchone()[0] == 0: + cursor.execute(''' + INSERT INTO llm_configs (name, provider, api_url, api_key, model, is_default) + VALUES ('智谱GLM', 'zhipu', 'https://open.bigmodel.cn/api/paas/v4/chat/completions', + '2259e33a1357460abe17919aaf81e73d.K44a8LPQTmFM5PKm', 'glm-4.5-air', 1) + ''') + + # 初始化默认搜索配置 + cursor.execute('SELECT COUNT(*) FROM search_configs') + if cursor.fetchone()[0] == 0: + cursor.execute(''' + INSERT INTO search_configs (name, provider, api_url, api_key, is_default) + VALUES ('Tavily', 'tavily', 'https://api.tavily.com/search', + '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', 1) + ''') + + # 初始化默认智能体 + cursor.execute('SELECT COUNT(*) FROM agents') + if cursor.fetchone()[0] == 0: + default_agents = [ + ('assistant', '通用助手', '🤖', 'hot', '能回答各类问题,帮助写作、分析、解答疑惑', '你是一个智能助手,能够回答各类问题,帮助用户解决问题。', 9500), + ('writer', '写作助手', '✍️', 'hot', '专注于文章写作、文案创作、内容润色', '你是一个专业的写作助手,擅长各类文章写作、文案创作和内容润色。', 8800), + ('coder', '编程助手', '👨‍💻', 'hot', '精通编程语言,解答技术问题,生成代码', '你是一个专业的编程助手,精通各类编程语言,能够解答技术问题并生成高质量代码。', 8500), + ('translator', '翻译助手', '🌐', 'hot', '多语言翻译,精准表达,文化适配', '你是一个专业的翻译助手,精通多语言翻译,能够精准表达并适配文化差异。', 7200), + ('work', '工作助手', '💼', 'work', '职场问题解答,工作效率提升', '你是一个工作助手,帮助解决职场问题,提升工作效率。', 5000), + ('ppt', 'PPT助手', '📊', 'work', 'PPT内容生成,结构优化,设计建议', '你是一个PPT助手,擅长PPT内容生成、结构优化和设计建议。', 4500), + ('excel', 'Excel助手', '📈', 'work', 'Excel公式、数据分析、表格优化', '你是一个Excel助手,精通Excel公式、数据分析和表格优化。', 4000), + ('teacher', '学习助手', '📚', 'study', '知识讲解,学习方法,考试辅导', '你是一个学习助手,擅长知识讲解、学习方法指导和考试辅导。', 5500), + ('english', '英语助手', '🔤', 'study', '英语学习,语法纠正,口语练习', '你是一个英语助手,帮助英语学习、语法纠正和口语练习。', 5000), + ('math', '数学助手', '🔢', 'study', '数学解题,公式推导,概念讲解', '你是一个数学助手,擅长数学解题、公式推导和概念讲解。', 4500), + ('health', '健康助手', '🏥', 'life', '健康咨询,养生建议,运动指导', '你是一个健康助手,提供健康咨询、养生建议和运动指导。', 3500), + ('travel', '旅行助手', '✈️', 'life', '旅行规划,景点推荐,美食指南', '你是一个旅行助手,擅长旅行规划、景点推荐和美食指南。', 3200), + ('food', '美食助手', '🍳', 'life', '菜谱推荐,烹饪技巧,营养搭配', '你是一个美食助手,提供菜谱推荐、烹饪技巧和营养搭配建议。', 3000), + ] + for agent in default_agents: + cursor.execute(''' + INSERT INTO agents (agent_id, name, avatar, category, description, system_prompt, heat) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''', agent) + + # 初始化系统配置 + cursor.execute('SELECT COUNT(*) FROM system_configs') + if cursor.fetchone()[0] == 0: + default_configs = [ + ('app_name', 'AI助手', '应用名称'), + ('app_version', '3.5.1', '应用版本'), + ('llm_provider', 'zhipu', '默认大模型提供商'), + ('enable_search', 'true', '是否启用联网搜索'), + ('guest_chat_sessions', '1', '游客每日对话会话限制'), + ('guest_chat_messages', '20', '游客每日对话消息限制'), + ('guest_agent_messages', '20', '游客每日智能体消息限制'), + ('admin_password', 'admin123', '管理员密码'), + ] + for key, value, desc in default_configs: + cursor.execute('INSERT INTO system_configs (key, value, description) VALUES (?, ?, ?)', (key, value, desc)) + + # 初始化管理员账户 + cursor.execute('SELECT COUNT(*) FROM admin_users') + if cursor.fetchone()[0] == 0: + cursor.execute('INSERT INTO admin_users (username, password_hash) VALUES (?, ?', + (ADMIN_USERNAME, ADMIN_PASSWORD_HASH)) + + conn.commit() + conn.close() + + +# ==================== 前端静态文件服务 ==================== + +@app.route('/') +def index(): + """前端首页""" + return send_from_directory(app.static_folder, 'index.html') + + +@app.route('/admin') +def admin(): + """后台管理页面""" + return send_from_directory(app.static_folder, 'admin.html') + + +@app.route('/admin/') +def admin_static(path): + """后台管理静态文件""" + return send_from_directory(app.static_folder, path) + + +@app.route('/') +def static_files(path): + """前端静态文件""" + return send_from_directory(app.static_folder, path) + + +# ==================== 后台管理 API ==================== + +# 管理员登录验证 +def check_admin_auth(): + """检查管理员权限""" + auth = request.authorization + if not auth: + return False + password_hash = hashlib.sha256(auth.password.encode()).hexdigest() + conn = get_db() + cursor = conn.cursor() + cursor.execute('SELECT * FROM admin_users WHERE username = ? AND password_hash = ?', + (auth.username, password_hash)) + user = cursor.fetchone() + conn.close() + return user is not None + + +@app.route('/api/admin/login', methods=['POST']) +def admin_login(): + """管理员登录""" + data = request.json + username = data.get('username') + password = data.get('password') + + if not username or not password: + return jsonify({'error': '请输入用户名和密码'}), 400 + + password_hash = hashlib.sha256(password.encode()).hexdigest() + conn = get_db() + cursor = conn.cursor() + cursor.execute('SELECT * FROM admin_users WHERE username = ? AND password_hash = ?', + (username, password_hash)) + user = cursor.fetchone() + conn.close() + + if user: + return jsonify({'success': True, 'message': '登录成功'}) + else: + return jsonify({'error': '用户名或密码错误'}), 401 + + +# ==================== 大模型接口管理 ==================== + +@app.route('/api/admin/llm', methods=['GET']) +def get_llm_configs(): + """获取所有大模型配置""" + conn = get_db() + cursor = conn.cursor() + cursor.execute('SELECT * FROM llm_configs ORDER BY is_default DESC, created_at DESC') + configs = [dict(row) for row in cursor.fetchall()] + conn.close() + return jsonify(configs) + + +@app.route('/api/admin/llm', methods=['POST']) +def add_llm_config(): + """添加大模型配置""" + data = request.json + conn = get_db() + cursor = conn.cursor() + cursor.execute(''' + INSERT INTO llm_configs (name, provider, api_url, api_key, model, max_tokens, temperature) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''', (data['name'], data['provider'], data['api_url'], data['api_key'], + data['model'], data.get('max_tokens', 2048), data.get('temperature', 0.7))) + conn.commit() + config_id = cursor.lastrowid + conn.close() + return jsonify({'success': True, 'id': config_id}) + + +@app.route('/api/admin/llm/', methods=['PUT']) +def update_llm_config(id): + """更新大模型配置""" + data = request.json + conn = get_db() + cursor = conn.cursor() + cursor.execute(''' + UPDATE llm_configs SET name=?, provider=?, api_url=?, api_key=?, model=?, + max_tokens=?, temperature=?, updated_at=CURRENT_TIMESTAMP WHERE id=? + ''', (data['name'], data['provider'], data['api_url'], data['api_key'], + data['model'], data.get('max_tokens', 2048), data.get('temperature', 0.7), id)) + conn.commit() + conn.close() + return jsonify({'success': True}) + + +@app.route('/api/admin/llm/', methods=['DELETE']) +def delete_llm_config(id): + """删除大模型配置""" + conn = get_db() + cursor = conn.cursor() + cursor.execute('DELETE FROM llm_configs WHERE id=?', (id,)) + conn.commit() + conn.close() + return jsonify({'success': True}) + + +@app.route('/api/admin/llm//default', methods=['POST']) +def set_default_llm(id): + """设置默认大模型""" + conn = get_db() + cursor = conn.cursor() + cursor.execute('UPDATE llm_configs SET is_default=0') + cursor.execute('UPDATE llm_configs SET is_default=1 WHERE id=?', (id,)) + conn.commit() + conn.close() + return jsonify({'success': True}) + + +# ==================== 智能体管理 ==================== + +@app.route('/api/admin/agents', methods=['GET']) +def get_agents(): + """获取所有智能体""" + conn = get_db() + cursor = conn.cursor() + cursor.execute('SELECT * FROM agents ORDER BY category, heat DESC') + agents = [dict(row) for row in cursor.fetchall()] + conn.close() + return jsonify(agents) + + +@app.route('/api/admin/agents', methods=['POST']) +def add_agent(): + """添加智能体""" + data = request.json + conn = get_db() + cursor = conn.cursor() + cursor.execute(''' + INSERT INTO agents (agent_id, name, avatar, category, description, system_prompt, + llm_config_id, temperature, max_tokens, enable_search, tags, heat) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', (data['agent_id'], data['name'], data.get('avatar', '🤖'), data['category'], + data.get('description', ''), data['system_prompt'], data.get('llm_config_id'), + data.get('temperature', 0.7), data.get('max_tokens', 2048), + data.get('enable_search', 0), data.get('tags', ''), data.get('heat', 0))) + conn.commit() + conn.close() + return jsonify({'success': True}) + + +@app.route('/api/admin/agents/', methods=['PUT']) +def update_agent(agent_id): + """更新智能体""" + data = request.json + conn = get_db() + cursor = conn.cursor() + cursor.execute(''' + UPDATE agents SET name=?, avatar=?, category=?, description=?, system_prompt=?, + llm_config_id=?, temperature=?, max_tokens=?, enable_search=?, tags=?, heat=?, + updated_at=CURRENT_TIMESTAMP WHERE agent_id=? + ''', (data['name'], data.get('avatar', '🤖'), data['category'], + data.get('description', ''), data['system_prompt'], data.get('llm_config_id'), + data.get('temperature', 0.7), data.get('max_tokens', 2048), + data.get('enable_search', 0), data.get('tags', ''), data.get('heat', 0), agent_id)) + conn.commit() + conn.close() + return jsonify({'success': True}) + + +@app.route('/api/admin/agents/', methods=['DELETE']) +def delete_agent(agent_id): + """删除智能体""" + conn = get_db() + cursor = conn.cursor() + cursor.execute('DELETE FROM agents WHERE agent_id=?', (agent_id,)) + conn.commit() + conn.close() + return jsonify({'success': True}) + + +# ==================== 搜索配置管理 ==================== + +@app.route('/api/admin/search', methods=['GET']) +def get_search_configs(): + """获取搜索配置""" + conn = get_db() + cursor = conn.cursor() + cursor.execute('SELECT * FROM search_configs ORDER BY is_default DESC') + configs = [dict(row) for row in cursor.fetchall()] + conn.close() + return jsonify(configs) + + +@app.route('/api/admin/search', methods=['POST']) +def add_search_config(): + """添加搜索配置""" + data = request.json + conn = get_db() + cursor = conn.cursor() + cursor.execute(''' + INSERT INTO search_configs (name, provider, api_url, api_key, max_results) + VALUES (?, ?, ?, ?, ?) + ''', (data['name'], data['provider'], data['api_url'], data['api_key'], + data.get('max_results', 10))) + conn.commit() + conn.close() + return jsonify({'success': True}) + + +@app.route('/api/admin/search/', methods=['PUT']) +def update_search_config(id): + """更新搜索配置""" + data = request.json + conn = get_db() + cursor = conn.cursor() + cursor.execute(''' + UPDATE search_configs SET name=?, provider=?, api_url=?, api_key=?, max_results=?, + updated_at=CURRENT_TIMESTAMP WHERE id=? + ''', (data['name'], data['provider'], data['api_url'], data['api_key'], + data.get('max_results', 10), id)) + conn.commit() + conn.close() + return jsonify({'success': True}) + + +@app.route('/api/admin/search/', methods=['DELETE']) +def delete_search_config(id): + """删除搜索配置""" + conn = get_db() + cursor = conn.cursor() + cursor.execute('DELETE FROM search_configs WHERE id=?', (id,)) + conn.commit() + conn.close() + return jsonify({'success': True}) + + +@app.route('/api/admin/search//default', methods=['POST']) +def set_default_search(id): + """设置默认搜索""" + conn = get_db() + cursor = conn.cursor() + cursor.execute('UPDATE search_configs SET is_default=0') + cursor.execute('UPDATE search_configs SET is_default=1 WHERE id=?', (id,)) + conn.commit() + conn.close() + return jsonify({'success': True}) + + +# ==================== 系统配置管理 ==================== + +@app.route('/api/admin/system', methods=['GET']) +def get_system_configs(): + """获取系统配置""" + conn = get_db() + cursor = conn.cursor() + cursor.execute('SELECT * FROM system_configs') + configs = {row['key']: {'value': row['value'], 'description': row['description']} + for row in cursor.fetchall()} + conn.close() + return jsonify(configs) + + +@app.route('/api/admin/system', methods=['POST']) +def update_system_config(): + """更新系统配置""" + data = request.json + conn = get_db() + cursor = conn.cursor() + for key, value in data.items(): + cursor.execute(''' + UPDATE system_configs SET value=?, updated_at=CURRENT_TIMESTAMP WHERE key=? + ''', (value, key)) + conn.commit() + conn.close() + return jsonify({'success': True}) + + +# ==================== 统计信息 ==================== + +@app.route('/api/admin/stats', methods=['GET']) +def get_stats(): + """获取统计信息""" + conn = get_db() + cursor = conn.cursor() + + # 今日统计 + today = datetime.now().strftime('%Y-%m-%d') + + # 各类型统计 + stats = {} + + # LLM调用统计 + cursor.execute('SELECT SUM(log_value) FROM stats_logs WHERE log_type="llm_call"') + stats['llm_total_calls'] = cursor.fetchone()[0] or 0 + + cursor.execute('SELECT SUM(log_value) FROM stats_logs WHERE log_type="llm_call" AND created_at >= ?', (today,)) + stats['llm_today_calls'] = cursor.fetchone()[0] or 0 + + # 搜索统计 + cursor.execute('SELECT SUM(log_value) FROM stats_logs WHERE log_type="search_call"') + stats['search_total_calls'] = cursor.fetchone()[0] or 0 + + cursor.execute('SELECT SUM(log_value) FROM stats_logs WHERE log_type="search_call" AND created_at >= ?', (today,)) + stats['search_today_calls'] = cursor.fetchone()[0] or 0 + + # 智能体使用统计 + cursor.execute(''' + SELECT log_key, SUM(log_value) as count FROM stats_logs + WHERE log_type="agent_use" GROUP BY log_key ORDER BY count DESC LIMIT 10 + ''') + stats['agent_usage'] = [dict(row) for row in cursor.fetchall()] + + # 用户统计 + cursor.execute('SELECT COUNT(*) FROM stats_logs WHERE log_type="user_register"') + stats['total_users'] = cursor.fetchone()[0] or 0 + + # 智能体数量 + cursor.execute('SELECT COUNT(*) FROM agents WHERE is_active=1') + stats['agent_count'] = cursor.fetchone()[0] + + # LLM配置数量 + cursor.execute('SELECT COUNT(*) FROM llm_configs WHERE is_active=1') + stats['llm_count'] = cursor.fetchone()[0] + + conn.close() + return jsonify(stats) + + +@app.route('/api/admin/stats/log', methods=['POST']) +def log_stats(): + """记录统计日志""" + data = request.json + conn = get_db() + cursor = conn.cursor() + cursor.execute('INSERT INTO stats_logs (log_type, log_key, log_value) VALUES (?, ?, ?)', + (data['type'], data.get('key', ''), data.get('value', 1))) + conn.commit() + conn.close() + return jsonify({'success': True}) + + +# ==================== 前端配置获取 ==================== + +@app.route('/api/config', methods=['GET']) +def get_frontend_config(): + """获取前端使用的配置""" + conn = get_db() + cursor = conn.cursor() + + # 获取默认LLM配置 + cursor.execute('SELECT * FROM llm_configs WHERE is_default=1 AND is_active=1 LIMIT 1') + llm = cursor.fetchone() + + # 获取默认搜索配置 + cursor.execute('SELECT * FROM search_configs WHERE is_default=1 AND is_active=1 LIMIT 1') + search = cursor.fetchone() + + # 获取所有智能体 + cursor.execute('SELECT agent_id, name, avatar, category, description, system_prompt, heat FROM agents WHERE is_active=1') + agents = [dict(row) for row in cursor.fetchall()] + + # 获取系统配置 + cursor.execute('SELECT key, value FROM system_configs') + system = {row['key']: row['value'] for row in cursor.fetchall()} + + conn.close() + + config = { + 'llm': dict(llm) if llm else None, + 'search': dict(search) if search else None, + 'agents': agents, + 'system': { + 'appName': system.get('app_name', 'AI助手'), + 'version': system.get('app_version', '3.5.1'), + 'enableSearch': system.get('enable_search', 'true') == 'true', + 'guestLimits': { + 'chatSessions': int(system.get('guest_chat_sessions', '1')), + 'chatMessages': int(system.get('guest_chat_messages', '20')), + 'agentMessages': int(system.get('guest_agent_messages', '20')), + } + } + } + + return jsonify(config) + + +# ==================== 启动 ==================== + +if __name__ == '__main__': + # 初始化数据库 + init_db() + + # 启动服务 + print('🚀 AI Chat App Backend started on http://localhost:19020') + print('📊 Admin Panel: http://localhost:19020/admin') + print('👤 Default admin: admin / admin123') + + app.run(host='0.0.0.0', port=19020, debug=True) \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..1a72c35 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,2 @@ +flask>=2.0.0 +flask-cors>=3.0.0 \ No newline at end of file diff --git a/backend/start.sh b/backend/start.sh new file mode 100755 index 0000000..ec8062b --- /dev/null +++ b/backend/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +cd "$(dirname "$0")" +pip install -r requirements.txt -q +python app.py \ No newline at end of file diff --git a/www/admin.html b/www/admin.html new file mode 100644 index 0000000..d5f8bf1 --- /dev/null +++ b/www/admin.html @@ -0,0 +1,471 @@ + + + + + + AI助手 - 后台管理 + + + + + + + +
+
+ +
+ 管理员 + +
+
+ +
+ + +
+ +
+
+
+ + +
+ + + + + + + \ No newline at end of file diff --git a/www/admin.js b/www/admin.js new file mode 100644 index 0000000..aa5a97e --- /dev/null +++ b/www/admin.js @@ -0,0 +1,865 @@ +// AI助手 - 后台管理前端 + +const API_BASE = ''; + +let currentPage = 'stats'; +let llmConfigs = []; +let agents = []; +let searchConfigs = []; +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 'search': + await loadSearchPage(content); + break; + case 'system': + await loadSystemPage(content); + break; + } +} + +// ==================== 统计页面 ==================== + +async function loadStatsPage(content) { + const stats = await fetchAPI('/api/admin/stats'); + + content.innerHTML = ` +
+

统计信息

+
+ +
+
+
🧠
+
${stats.llm_total_calls || 0}
+
LLM 总调用
+
+
+
📊
+
${stats.llm_today_calls || 0}
+
今日 LLM 调用
+
+
+
🔍
+
${stats.search_total_calls || 0}
+
搜索总调用
+
+
+
📈
+
${stats.search_today_calls || 0}
+
今日搜索
+
+
+ +
+
+
🤖
+
${stats.agent_count || 0}
+
智能体数量
+
+
+
🧠
+
${stats.llm_count || 0}
+
LLM 配置数量
+
+
+
👥
+
${stats.total_users || 0}
+
注册用户数
+
+
+
📅
+
${new Date().toLocaleDateString()}
+
统计日期
+
+
+ +
+

热门智能体使用排行

+ + + + + + + + + ${stats.agent_usage?.map(a => ` + + + + + `).join('') || ''} + +
智能体使用次数
${a.log_key}${a.count}
暂无数据
+
+ `; +} + +// ==================== 大模型配置页面 ==================== + +async function loadLLMPage(content) { + llmConfigs = await fetchAPI('/api/admin/llm'); + + content.innerHTML = ` +
+

大模型配置

+ +
+ +
+ + + + + + + + + + + + + ${llmConfigs.map(c => ` + + + + + + + + + `).join('')} + +
名称提供商模型API URL状态操作
${c.name} ${c.is_default ? '默认' : ''}${c.provider}${c.model}${c.api_url}${c.is_active ? '✅ 启用' : '❌ 禁用'} +
+ + ${!c.is_default ? `` : ''} + +
+
+
+ `; +} + +function showAddLLMModal() { + showModal('添加大模型配置', ` +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + `); +} + +function showEditLLMModal(id) { + const config = llmConfigs.find(c => c.id === id); + if (!config) return; + + showModal('编辑大模型配置', ` +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + `); +} + +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 = ` +
+

智能体管理

+ +
+ +
+ + + + + + + + + + + + + + ${agents.map(a => ` + + + + + + + + + + `).join('')} + +
头像名称类别描述热度状态操作
${a.avatar}${a.name}${categories[a.category] || a.category}${a.description || '-'}${a.heat || 0}${a.is_active ? '✅ 启用' : '❌ 禁用'} +
+ + +
+
+
+ `; +} + +function showAddAgentModal() { + showModal('添加智能体', ` +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + `); +} + +function showEditAgentModal(agentId) { + const agent = agents.find(a => a.agent_id === agentId); + if (!agent) return; + + showModal('编辑智能体', ` +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + `); +} + +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 loadSearchPage(content) { + searchConfigs = await fetchAPI('/api/admin/search'); + + content.innerHTML = ` +
+

搜索配置

+ +
+ +
+ + + + + + + + + + + + + ${searchConfigs.map(c => ` + + + + + + + + + `).join('')} + +
名称提供商API URL最大结果数状态操作
${c.name} ${c.is_default ? '默认' : ''}${c.provider}${c.api_url}${c.max_results}${c.is_active ? '✅ 启用' : '❌ 禁用'} +
+ + ${!c.is_default ? `` : ''} + +
+
+
+ `; +} + +function showAddSearchModal() { + showModal('添加搜索配置', ` +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + `); +} + +function showEditSearchModal(id) { + const config = searchConfigs.find(c => c.id === id); + if (!config) return; + + showModal('编辑搜索配置', ` +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + `); +} + +async function saveSearch() { + 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) + }; + + if (!data.name || !data.api_url || !data.api_key) { + showToast('请填写完整信息'); + return; + } + + await fetchAPI('/api/admin/search', 'POST', data); + closeModal(); + showToast('添加成功'); + loadPage('search'); +} + +async function updateSearch(id) { + 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) + }; + + await fetchAPI(`/api/admin/search/${id}`, 'PUT', data); + closeModal(); + showToast('更新成功'); + loadPage('search'); +} + +async function setDefaultSearch(id) { + await fetchAPI(`/api/admin/search/${id}/default`, 'POST'); + showToast('已设为默认'); + loadPage('search'); +} + +async function deleteSearch(id) { + if (!confirm('确定删除此配置?')) return; + await fetchAPI(`/api/admin/search/${id}`, 'DELETE'); + showToast('删除成功'); + loadPage('search'); +} + +// ==================== 系统设置页面 ==================== + +async function loadSystemPage(content) { + systemConfigs = await fetchAPI('/api/admin/system'); + + content.innerHTML = ` +
+

系统设置

+
+ +
+
+ + + ${systemConfigs.app_name?.description || ''} +
+ +
+ + +
+ +
+ +
+ +

游客使用限制

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +

管理员设置

+ +
+ + +
+ + +
+ `; +} + +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 = ` + + ${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(); + } +}); \ No newline at end of file