#!/usr/bin/env python3 """ AI Chat App - 后台管理服务 端口: 19021 (与前端同一端口) """ from flask import Flask, jsonify, request, send_from_directory, Response from flask_cors import CORS import os import json import sqlite3 from datetime import datetime import hashlib import base64 import asyncio import edge_tts app = Flask(__name__, static_folder='../www') CORS(app) # 数据库路径 DB_PATH = os.path.join(os.path.dirname(__file__), 'data.db') # 头像存储目录 AVATAR_DIR = os.path.join(os.path.dirname(__file__), 'avatars') if not os.path.exists(AVATAR_DIR): os.makedirs(AVATAR_DIR) # 管理员账户(默认) 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_tools TEXT, tags TEXT, heat INTEGER DEFAULT 0, is_online INTEGER DEFAULT 1, 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 tool_configs ( id INTEGER PRIMARY KEY AUTOINCREMENT, tool_id TEXT NOT NULL UNIQUE, name TEXT NOT NULL, type TEXT NOT NULL, provider TEXT NOT NULL, api_url TEXT NOT NULL, api_key TEXT, config_json TEXT, 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 chat_configs ( id INTEGER PRIMARY KEY AUTOINCREMENT, config_id TEXT NOT NULL UNIQUE, name TEXT NOT NULL, llm_config_id INTEGER, enable_search INTEGER DEFAULT 1, enable_tools TEXT, max_history INTEGER DEFAULT 20, temperature REAL DEFAULT 0.7, system_prompt TEXT, is_default 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 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(''' CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, phone TEXT NOT NULL UNIQUE, email TEXT, avatar TEXT DEFAULT '👤', signature TEXT, gender TEXT, age INTEGER, region TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_login_at TIMESTAMP ) ''') # 用户智能体配置表(我的智能体) cursor.execute(''' CREATE TABLE IF NOT EXISTS user_agents ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, agent_id TEXT NOT NULL, category TEXT NOT NULL, is_pinned INTEGER DEFAULT 0, is_favorite INTEGER DEFAULT 0, added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id), UNIQUE(user_id, agent_id) ) ''') # 对话表(用户对话数据) cursor.execute(''' CREATE TABLE IF NOT EXISTS conversations ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, title TEXT NOT NULL, agent_id TEXT, messages TEXT NOT NULL DEFAULT '[]', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ) ''') # 初始化默认大模型配置 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 tool_configs') if cursor.fetchone()[0] == 0: cursor.execute(''' INSERT INTO tool_configs (tool_id, name, type, provider, api_url, api_key, max_results, is_default) VALUES ('search', '联网搜索', 'search', 'tavily', 'https://api.tavily.com/search', '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', 10, 1) ''') # 初始化默认对话配置 cursor.execute('SELECT COUNT(*) FROM chat_configs') if cursor.fetchone()[0] == 0: cursor.execute(''' INSERT INTO chat_configs (config_id, name, llm_config_id, enable_tools, is_default) VALUES ('default', '默认对话配置', 1, 'search', 1) ''') # 初始化默认智能体 cursor.execute('SELECT COUNT(*) FROM agents') if cursor.fetchone()[0] == 0: default_agents = [ # 基础类别 ('assistant', '通用助手', '🤖', 'basic', '能回答各类问题,帮助写作、分析、解答疑惑', '你是一个智能助手,能够回答各类问题,帮助用户解决问题。', 'hot', 9500), ('writer', '写作助手', '✍️', 'basic', '专注于文章写作、文案创作、内容润色', '你是一个专业的写作助手,擅长各类文章写作、文案创作和内容润色。', 'hot', 8800), ('coder', '编程助手', '👨‍💻', 'basic', '精通编程语言,解答技术问题,生成代码', '你是一个专业的编程助手,精通各类编程语言,能够解答技术问题并生成高质量代码。', 'hot', 8500), ('translator', '翻译助手', '🌐', 'basic', '多语言翻译,精准表达,文化适配', '你是一个专业的翻译助手,精通多语言翻译,能够精准表达并适配文化差异。', 'hot', 7200), # 工作类别 ('work', '工作助手', '💼', 'work', '职场问题解答,工作效率提升', '你是一个工作助手,帮助解决职场问题,提升工作效率。', 'popular', 5000), ('ppt', 'PPT助手', '📊', 'work', 'PPT内容生成,结构优化,设计建议', '你是一个PPT助手,擅长PPT内容生成、结构优化和设计建议。', 'popular', 4500), ('excel', 'Excel助手', '📈', 'work', 'Excel公式、数据分析、表格优化', '你是一个Excel助手,精通Excel公式、数据分析和表格优化。', '', 4000), # 学习类别 ('teacher', '学习助手', '📚', 'study', '知识讲解,学习方法,考试辅导', '你是一个学习助手,擅长知识讲解、学习方法指导和考试辅导。', 'popular', 5500), ('english', '英语助手', '🔤', 'study', '英语学习,语法纠正,口语练习', '你是一个英语助手,帮助英语学习、语法纠正和口语练习。', '', 5000), ('math', '数学助手', '🔢', 'study', '数学解题,公式推导,概念讲解', '你是一个数学助手,擅长数学解题、公式推导和概念讲解。', '', 4500), # 生活类别 ('health', '健康助手', '🏥', 'life', '健康咨询,养生建议,运动指导', '你是一个健康助手,提供健康咨询、养生建议和运动指导。', '', 3500), ('travel', '旅行助手', '✈️', 'life', '旅行规划,景点推荐,美食指南', '你是一个旅行助手,擅长旅行规划、景点推荐和美食指南。', 'popular', 3200), ('food', '美食助手', '🍳', 'life', '菜谱推荐,烹饪技巧,营养搭配', '你是一个美食助手,提供菜谱推荐、烹饪技巧和营养搭配建议。', '', 3000), ] for agent in default_agents: cursor.execute(''' INSERT INTO agents (agent_id, name, avatar, category, description, system_prompt, tags, heat, is_online) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1) ''', agent) # 初始化系统配置 cursor.execute('SELECT COUNT(*) FROM system_configs') if cursor.fetchone()[0] == 0: default_configs = [ ('app_name', 'AI助手', '应用名称'), ('app_version', '3.10.0', '应用版本'), ('llm_provider', 'zhipu', '默认大模型提供商'), ('enable_search', 'true', '是否启用联网搜索'), ('guest_chat_sessions', '1', '游客每日对话会话限制'), ('guest_chat_messages', '20', '游客每日对话消息限制'), ('guest_agent_messages', '20', '游客每日智能体消息限制'), ('admin_password', 'admin123', '管理员密码'), ('app_developer', 'OpenClaw Team', '开发者'), ('app_update_date', '2026-04-27', '更新日期'), ('app_technology', '智谱 GLM-4.5-Air 大模型', '技术基础'), ('app_description', '提供智能对话、多种智能体服务', '应用简介'), ('privacy_policy_url', '', '隐私政策链接'), ('user_agreement_url', '', '用户协议链接'), ('tts_provider', 'edge', 'TTS方案'), ('tts_voice', 'zh-CN-XiaoxiaoNeural', 'TTS语音'), ] for key, value, desc in default_configs: cursor.execute('INSERT INTO system_configs (key, value, description) VALUES (?, ?, ?)', (key, value, desc)) else: # 检查并添加缺失的配置项(兼容旧数据库) default_configs = [ ('app_developer', 'OpenClaw Team', '开发者'), ('app_update_date', '2026-04-27', '更新日期'), ('app_technology', '智谱 GLM-4.5-Air 大模型', '技术基础'), ('app_description', '提供智能对话、多种智能体服务', '应用简介'), ('privacy_policy_url', '', '隐私政策链接'), ('user_agreement_url', '', '用户协议链接'), ('tts_provider', 'edge', 'TTS方案'), ('tts_voice', 'zh-CN-XiaoxiaoNeural', 'TTS语音'), ] for key, value, desc in default_configs: cursor.execute('SELECT COUNT(*) FROM system_configs WHERE key=?', (key,)) if cursor.fetchone()[0] == 0: 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/register', methods=['POST']) def user_register(): """用户注册""" data = request.json username = data.get('username') password = data.get('password') phone = data.get('phone') email = data.get('email') code = data.get('code') # 验证码(暂时只校验格式) # 参数验证 if not username or not password or not phone: return jsonify({'error': '请填写完整信息'}), 400 # 用户名长度检查 if len(username) < 2 or len(username) > 20: return jsonify({'error': '用户名长度应为2-20字符'}), 400 # 手机号格式检查 import re if not re.match(r'^1[3-9]\d{9}$', phone): return jsonify({'error': '手机号格式不正确'}), 400 # 验证码检查(模拟,6位数字) if not code or not re.match(r'^\d{6}$', code): return jsonify({'error': '验证码格式不正确'}), 400 # 邮箱格式检查(可选) if email and not re.match(r'^[^\s@]+@[^\s@]+\.[^\s@]+$', email): return jsonify({'error': '邮箱格式不正确'}), 400 conn = get_db() cursor = conn.cursor() # 检查用户名是否已存在 cursor.execute('SELECT id FROM users WHERE username = ?', (username,)) if cursor.fetchone(): conn.close() return jsonify({'error': '用户名已存在'}), 400 # 检查手机号是否已存在 cursor.execute('SELECT id FROM users WHERE phone = ?', (phone,)) if cursor.fetchone(): conn.close() return jsonify({'error': '手机号已注册'}), 400 # 创建用户 password_hash = hashlib.sha256(password.encode()).hexdigest() cursor.execute(''' INSERT INTO users (username, password_hash, phone, email) VALUES (?, ?, ?, ?) ''', (username, password_hash, phone, email)) # 记录注册统计 cursor.execute('INSERT INTO stats_logs (log_type, log_key) VALUES (?, ?)', ('user_register', 'new')) conn.commit() user_id = cursor.lastrowid conn.close() return jsonify({'success': True, 'user_id': user_id}) @app.route('/api/login', methods=['POST']) def user_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 users WHERE username = ? AND password_hash = ?', (username, password_hash)) user = cursor.fetchone() if user: # 更新最后登录时间 cursor.execute('UPDATE users SET last_login_at = CURRENT_TIMESTAMP WHERE id = ?', (user['id'],)) conn.commit() conn.close() return jsonify({ 'success': True, 'user': { 'id': user['id'], 'username': user['username'], 'phone': user['phone'], 'email': user['email'], 'avatar': user['avatar'], 'signature': user['signature'], 'gender': user['gender'], 'age': user['age'], 'region': user['region'] } }) else: conn.close() return jsonify({'error': '用户名或密码错误'}), 401 @app.route('/api/admin/users', methods=['GET']) def get_users(): """获取用户列表""" conn = get_db() cursor = conn.cursor() # 支持搜索 search = request.args.get('search', '') if search: cursor.execute(''' SELECT id, username, phone, email, avatar, signature, gender, age, region, created_at, last_login_at FROM users WHERE username LIKE ? OR phone LIKE ? OR email LIKE ? ORDER BY created_at DESC ''', (f'%{search}%', f'%{search}%', f'%{search}%')) else: cursor.execute(''' SELECT id, username, phone, email, avatar, signature, gender, age, region, created_at, last_login_at FROM users ORDER BY created_at DESC ''') users = [dict(row) for row in cursor.fetchall()] conn.close() return jsonify(users) @app.route('/api/admin/users/', methods=['GET']) @app.route('/api/user/', methods=['GET']) def get_user(id): """获取单个用户信息""" conn = get_db() cursor = conn.cursor() cursor.execute('SELECT * FROM users WHERE id = ?', (id,)) user = cursor.fetchone() conn.close() if user: return jsonify(dict(user)) else: return jsonify({'error': '用户不存在'}), 404 @app.route('/api/admin/users/', methods=['PUT']) def update_user(id): """更新用户信息""" data = request.json conn = get_db() cursor = conn.cursor() # 检查用户是否存在 cursor.execute('SELECT id FROM users WHERE id = ?', (id,)) if not cursor.fetchone(): conn.close() return jsonify({'error': '用户不存在'}), 404 # 检查用户名是否重复(如果要修改用户名) if data.get('username'): cursor.execute('SELECT id FROM users WHERE username = ? AND id != ?', (data['username'], id)) if cursor.fetchone(): conn.close() return jsonify({'error': '用户名已存在'}), 400 # 更新用户信息 cursor.execute(''' UPDATE users SET username=?, email=?, avatar=?, signature=?, gender=?, age=?, region=?, updated_at=CURRENT_TIMESTAMP WHERE id=? ''', (data.get('username'), data.get('email'), data.get('avatar', '👤'), data.get('signature'), data.get('gender'), data.get('age'), data.get('region'), id)) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/admin/users/', methods=['DELETE']) def delete_user(id): """删除用户""" conn = get_db() cursor = conn.cursor() cursor.execute('DELETE FROM users WHERE id = ?', (id,)) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/admin/users//password', methods=['PUT']) def reset_user_password(id): """重置用户密码""" data = request.json new_password = data.get('password') if not new_password or len(new_password) < 6: return jsonify({'error': '密码长度至少6位'}), 400 password_hash = hashlib.sha256(new_password.encode()).hexdigest() conn = get_db() cursor = conn.cursor() cursor.execute('UPDATE users SET password_hash=?, updated_at=CURRENT_TIMESTAMP WHERE id=?', (password_hash, id)) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/user/', methods=['PUT']) def update_user_profile(id): """用户更新自己的资料""" data = request.json conn = get_db() cursor = conn.cursor() # 检查用户是否存在 cursor.execute('SELECT id FROM users WHERE id = ?', (id,)) if not cursor.fetchone(): conn.close() return jsonify({'error': '用户不存在'}), 404 # 更新用户资料 cursor.execute(''' UPDATE users SET avatar=?, signature=?, gender=?, age=?, region=?, email=?, updated_at=CURRENT_TIMESTAMP WHERE id=? ''', (data.get('avatar', '👤'), data.get('signature'), data.get('gender'), data.get('age'), data.get('region'), data.get('email'), id)) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/user//password', methods=['PUT']) def change_user_password(id): """用户修改自己的密码""" data = request.json old_password = data.get('old_password') new_password = data.get('new_password') if not old_password or not new_password: return jsonify({'error': '请输入旧密码和新密码'}), 400 if len(new_password) < 6: return jsonify({'error': '新密码长度至少6位'}), 400 old_hash = hashlib.sha256(old_password.encode()).hexdigest() new_hash = hashlib.sha256(new_password.encode()).hexdigest() conn = get_db() cursor = conn.cursor() # 验证旧密码 cursor.execute('SELECT id FROM users WHERE id = ? AND password_hash = ?', (id, old_hash)) if not cursor.fetchone(): conn.close() return jsonify({'error': '旧密码不正确'}), 400 # 更新密码 cursor.execute('UPDATE users SET password_hash=?, updated_at=CURRENT_TIMESTAMP WHERE id=?', (new_hash, id)) conn.commit() conn.close() return jsonify({'success': True}) # ==================== 用户头像上传 ==================== @app.route('/api/user//avatar', methods=['POST']) def upload_user_avatar(user_id): """上传用户头像""" data = request.json avatar_data = data.get('avatar') # base64 格式图片 if not avatar_data: return jsonify({'error': '头像数据不能为空'}), 400 # 解析 base64 数据 try: # 支持两种格式: # 1. data:image/png;base64,xxxxx # 2. 纯 base64 字符串 if avatar_data.startswith('data:'): # 提取格式和内容 header, content = avatar_data.split(',', 1) # 提取图片格式 mime_type = header.split(':')[1].split(';')[0] ext = mime_type.split('/')[1] if '/' in mime_type else 'png' else: content = avatar_data ext = 'png' # 解码 base64 image_bytes = base64.b64decode(content) # 验证图片大小(最大 2MB) if len(image_bytes) > 2 * 1024 * 1024: return jsonify({'error': '头像大小不能超过2MB'}), 400 # 生成文件名 filename = f'user_{user_id}_{int(datetime.now().timestamp())}.{ext}' filepath = os.path.join(AVATAR_DIR, filename) # 保存文件 with open(filepath, 'wb') as f: f.write(image_bytes) # 更新数据库(存储文件名) conn = get_db() cursor = conn.cursor() cursor.execute('UPDATE users SET avatar=?, updated_at=CURRENT_TIMESTAMP WHERE id=?', (filename, user_id)) conn.commit() conn.close() return jsonify({'success': True, 'avatar': filename, 'avatar_url': f'/api/avatars/{filename}'}) except Exception as e: return jsonify({'error': f'头像上传失败: {str(e)}'}), 500 @app.route('/api/avatars/', methods=['GET']) def get_avatar(filename): """获取头像图片""" return send_from_directory(AVATAR_DIR, filename) @app.route('/api/admin/users//avatar', methods=['POST']) def admin_upload_user_avatar(user_id): """管理员上传用户头像""" return upload_user_avatar(user_id) # ==================== 用户对话数据同步 ==================== @app.route('/api/user//conversations', methods=['GET']) def get_user_conversations(user_id): """获取用户所有对话""" conn = get_db() cursor = conn.cursor() cursor.execute(''' SELECT id, title, agent_id, messages, created_at, updated_at FROM conversations WHERE user_id = ? ORDER BY updated_at DESC ''', (user_id,)) conversations = [] for row in cursor.fetchall(): conv = dict(row) # 解析消息JSON try: conv['messages'] = json.loads(conv['messages']) if conv['messages'] else [] except: conv['messages'] = [] # 转换为前端格式(使用字符串ID) conv['id'] = str(conv['id']) conv['createdAt'] = int(datetime.strptime(conv['created_at'], '%Y-%m-%d %H:%M:%S').timestamp() * 1000) if conv['created_at'] else 0 conv['updatedAt'] = int(datetime.strptime(conv['updated_at'], '%Y-%m-%d %H:%M:%S').timestamp() * 1000) if conv['updated_at'] else 0 conv['agentId'] = conv['agent_id'] conversations.append(conv) conn.close() return jsonify(conversations) @app.route('/api/user//conversations/', methods=['GET']) def get_user_conversation_detail(user_id, conv_id): """获取单个对话详情""" conn = get_db() cursor = conn.cursor() cursor.execute(''' SELECT id, title, agent_id, messages, created_at, updated_at FROM conversations WHERE id = ? AND user_id = ? ''', (conv_id, user_id)) row = cursor.fetchone() conn.close() if not row: return jsonify({'error': '对话不存在'}), 404 conv = dict(row) try: conv['messages'] = json.loads(conv['messages']) if conv['messages'] else [] except: conv['messages'] = [] conv['id'] = str(conv['id']) conv['createdAt'] = int(datetime.strptime(conv['created_at'], '%Y-%m-%d %H:%M:%S').timestamp() * 1000) if conv['created_at'] else 0 conv['updatedAt'] = int(datetime.strptime(conv['updated_at'], '%Y-%m-%d %H:%M:%S').timestamp() * 1000) if conv['updated_at'] else 0 conv['agentId'] = conv['agent_id'] return jsonify(conv) @app.route('/api/user//conversations', methods=['POST']) def create_user_conversation(user_id): """创建新对话""" data = request.json title = data.get('title', '新对话') agent_id = data.get('agentId') or data.get('agent_id') messages = data.get('messages', []) conn = get_db() cursor = conn.cursor() cursor.execute(''' INSERT INTO conversations (user_id, title, agent_id, messages) VALUES (?, ?, ?, ?) ''', (user_id, title, agent_id, json.dumps(messages))) conn.commit() conv_id = cursor.lastrowid conn.close() return jsonify({'success': True, 'id': str(conv_id)}) @app.route('/api/user//conversations/', methods=['PUT']) def update_user_conversation(user_id, conv_id): """更新对话(添加消息、修改标题等)""" data = request.json conn = get_db() cursor = conn.cursor() # 验证对话属于该用户 cursor.execute('SELECT id FROM conversations WHERE id = ? AND user_id = ?', (conv_id, user_id)) if not cursor.fetchone(): conn.close() return jsonify({'error': '对话不存在'}), 404 # 更新字段 updates = [] values = [] if 'title' in data: updates.append('title = ?') values.append(data['title']) if 'messages' in data: updates.append('messages = ?') values.append(json.dumps(data['messages'])) if 'agentId' in data or 'agent_id' in data: updates.append('agent_id = ?') values.append(data.get('agentId') or data.get('agent_id')) if updates: updates.append('updated_at = CURRENT_TIMESTAMP') sql = f'UPDATE conversations SET {", ".join(updates)} WHERE id = ?' values.append(conv_id) cursor.execute(sql, values) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/user//conversations/', methods=['DELETE']) def delete_user_conversation(user_id, conv_id): """删除对话""" conn = get_db() cursor = conn.cursor() # 验证对话属于该用户 cursor.execute('SELECT id FROM conversations WHERE id = ? AND user_id = ?', (conv_id, user_id)) if not cursor.fetchone(): conn.close() return jsonify({'error': '对话不存在'}), 404 cursor.execute('DELETE FROM conversations WHERE id = ?', (conv_id,)) conn.commit() conn.close() return jsonify({'success': True}) # ==================== 用户智能体数据同步 ==================== @app.route('/api/user//agents', methods=['GET']) def get_user_agents(user_id): """获取用户智能体配置""" conn = get_db() cursor = conn.cursor() cursor.execute(''' SELECT agent_id, category, is_pinned, is_favorite, added_at FROM user_agents WHERE user_id = ? ''', (user_id,)) agents_data = { 'myAgents': {}, # {category: [agent_ids]} 'favoriteAgents': [], # [agent_ids] 'pinnedAgents': {} # {category: [agent_ids]} } for row in cursor.fetchall(): agent_id = row['agent_id'] category = row['category'] is_pinned = row['is_pinned'] is_favorite = row['is_favorite'] # 添加到 myAgents if category not in agents_data['myAgents']: agents_data['myAgents'][category] = [] agents_data['myAgents'][category].append(agent_id) # 添加到 pinnedAgents if is_pinned: if category not in agents_data['pinnedAgents']: agents_data['pinnedAgents'][category] = [] agents_data['pinnedAgents'][category].append(agent_id) # 添加到 favoriteAgents if is_favorite: agents_data['favoriteAgents'].append(agent_id) conn.close() return jsonify(agents_data) @app.route('/api/user//agents/', methods=['POST']) def add_user_agent(user_id, agent_id): """添加智能体到用户列表""" data = request.json category = data.get('category', 'basic') is_pinned = data.get('is_pinned', 0) is_favorite = data.get('is_favorite', 0) conn = get_db() cursor = conn.cursor() try: cursor.execute(''' INSERT INTO user_agents (user_id, agent_id, category, is_pinned, is_favorite) VALUES (?, ?, ?, ?, ?) ''', (user_id, agent_id, category, is_pinned, is_favorite)) conn.commit() conn.close() return jsonify({'success': True}) except: # 已存在,更新 cursor.execute(''' UPDATE user_agents SET category=?, is_pinned=?, is_favorite=? WHERE user_id=? AND agent_id=? ''', (category, is_pinned, is_favorite, user_id, agent_id)) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/user//agents/', methods=['DELETE']) def remove_user_agent(user_id, agent_id): """从用户列表移除智能体""" conn = get_db() cursor = conn.cursor() cursor.execute('DELETE FROM user_agents WHERE user_id=? AND agent_id=?', (user_id, agent_id)) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/user//agents//pin', methods=['POST']) def toggle_user_agent_pin(user_id, agent_id): """切换智能体置顶状态""" data = request.json is_pinned = data.get('is_pinned', 1) category = data.get('category', 'basic') conn = get_db() cursor = conn.cursor() # 检查是否存在 cursor.execute('SELECT id FROM user_agents WHERE user_id=? AND agent_id=?', (user_id, agent_id)) if cursor.fetchone(): cursor.execute('UPDATE user_agents SET is_pinned=? WHERE user_id=? AND agent_id=?', (is_pinned, user_id, agent_id)) else: cursor.execute('INSERT INTO user_agents (user_id, agent_id, category, is_pinned) VALUES (?, ?, ?, ?)', (user_id, agent_id, category, is_pinned)) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/user//agents//favorite', methods=['POST']) def toggle_user_agent_favorite(user_id, agent_id): """切换智能体收藏状态""" data = request.json is_favorite = data.get('is_favorite', 1) category = data.get('category', 'basic') conn = get_db() cursor = conn.cursor() # 检查是否存在 cursor.execute('SELECT id FROM user_agents WHERE user_id=? AND agent_id=?', (user_id, agent_id)) if cursor.fetchone(): cursor.execute('UPDATE user_agents SET is_favorite=? WHERE user_id=? AND agent_id=?', (is_favorite, user_id, agent_id)) else: cursor.execute('INSERT INTO user_agents (user_id, agent_id, category, is_favorite) VALUES (?, ?, ?, ?)', (user_id, agent_id, category, is_favorite)) conn.commit() conn.close() return jsonify({'success': True}) # ==================== 大模型接口管理 ==================== @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_tools, tags, heat, is_online) 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_tools', ''), data.get('tags', ''), data.get('heat', 0), data.get('is_online', 1))) 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_tools=?, tags=?, heat=?, is_online=?, 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_tools', ''), data.get('tags', ''), data.get('heat', 0), data.get('is_online', 1), 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/agents//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']) def get_chat_config(): """获取对话配置(只有一个默认配置)""" conn = get_db() cursor = conn.cursor() cursor.execute('SELECT * FROM chat_configs WHERE is_default=1 LIMIT 1') config = cursor.fetchone() if not config: # 如果没有配置,创建默认配置 cursor.execute(''' INSERT INTO chat_configs (config_id, name, llm_config_id, enable_search, enable_tools, max_history, temperature, is_default) VALUES ('default', '默认配置', 1, 1, 'search', 20, 0.7, 1) ''') conn.commit() cursor.execute('SELECT * FROM chat_configs WHERE is_default=1 LIMIT 1') config = cursor.fetchone() conn.close() return jsonify(dict(config) if config else {}) @app.route('/api/admin/chat', methods=['PUT']) def update_chat_config(): """更新对话配置""" data = request.json conn = get_db() cursor = conn.cursor() cursor.execute(''' UPDATE chat_configs SET llm_config_id=?, enable_search=?, enable_tools=?, max_history=?, temperature=?, system_prompt=?, updated_at=CURRENT_TIMESTAMP WHERE is_default=1 ''', (data.get('llm_config_id', 1), data.get('enable_search', 1), data.get('enable_tools', 'search'), data.get('max_history', 20), data.get('temperature', 0.7), data.get('system_prompt', ''))) conn.commit() conn.close() return jsonify({'success': True}) # ==================== 工具配置管理 ==================== @app.route('/api/admin/tools', methods=['GET']) def get_tool_configs(): """获取工具配置""" conn = get_db() cursor = conn.cursor() cursor.execute('SELECT * FROM tool_configs ORDER BY is_default DESC, type') tools = [dict(row) for row in cursor.fetchall()] conn.close() return jsonify(tools) @app.route('/api/admin/tools', methods=['POST']) def add_tool_config(): """添加工具配置""" data = request.json conn = get_db() cursor = conn.cursor() cursor.execute(''' INSERT INTO tool_configs (tool_id, name, type, provider, api_url, api_key, config_json, max_results) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ''', (data['tool_id'], data['name'], data['type'], data['provider'], data['api_url'], data.get('api_key', ''), data.get('config_json', ''), data.get('max_results', 10))) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/admin/tools/', methods=['PUT']) def update_tool_config(tool_id): """更新工具配置""" data = request.json conn = get_db() cursor = conn.cursor() cursor.execute(''' UPDATE tool_configs SET name=?, type=?, provider=?, api_url=?, api_key=?, config_json=?, max_results=?, updated_at=CURRENT_TIMESTAMP WHERE tool_id=? ''', (data['name'], data['type'], data['provider'], data['api_url'], data.get('api_key', ''), data.get('config_json', ''), data.get('max_results', 10), tool_id)) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/admin/tools/', methods=['DELETE']) def delete_tool_config(tool_id): """删除工具配置""" conn = get_db() cursor = conn.cursor() cursor.execute('DELETE FROM tool_configs WHERE tool_id=?', (tool_id,)) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/admin/tools//default', methods=['POST']) def set_default_tool(tool_id): """设置默认工具""" conn = get_db() cursor = conn.cursor() cursor.execute('UPDATE tool_configs SET is_default=0 WHERE type=(SELECT type FROM tool_configs WHERE tool_id=?)', (tool_id,)) cursor.execute('UPDATE tool_configs SET is_default=1 WHERE tool_id=?', (tool_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 tool_configs WHERE is_default=1 AND is_active=1') tools = [dict(row) for row in cursor.fetchall()] # 获取所有活跃的工具配置(供前端选择) cursor.execute('SELECT tool_id, name, type, provider, is_active FROM tool_configs WHERE is_active=1') allTools = [dict(row) for row in cursor.fetchall()] # 获取所有智能体(上线且活跃) 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()] # 获取默认对话配置 cursor.execute('SELECT * FROM chat_configs WHERE is_default=1 LIMIT 1') chat_config = cursor.fetchone() # 获取系统配置 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, 'tools': tools, 'allTools': allTools, # 所有活跃的工具(供前端选择) 'agents': agents, 'chat_config': dict(chat_config) if chat_config else None, 'system': { 'appName': system.get('app_name', 'AI助手'), 'version': system.get('app_version', '3.10.0'), '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')), }, 'developer': system.get('app_developer', 'OpenClaw Team'), 'updateDate': system.get('app_update_date', '2026-04-27'), 'technology': system.get('app_technology', '智谱 GLM-4.5-Air 大模型'), 'description': system.get('app_description', '提供智能对话、多种智能体服务'), 'privacyPolicyUrl': system.get('privacy_policy_url', ''), 'userAgreementUrl': system.get('user_agreement_url', ''), 'ttsProvider': system.get('tts_provider', 'edge'), 'ttsVoice': system.get('tts_voice', 'zh-CN-XiaoxiaoNeural'), } } return jsonify(config) # ==================== TTS 语音合成 ==================== @app.route('/api/tts', methods=['POST']) def generate_tts(): """使用 Edge TTS 生成语音""" data = request.json text = data.get('text', '') voice = data.get('voice', 'zh-CN-XiaoxiaoNeural') # 默认中文女声 if not text: return jsonify({'error': '缺少文本内容'}), 400 try: # 使用 asyncio 运行 edge_tts async def generate_audio(): communicate = edge_tts.Communicate(text, voice) audio_data = b'' for chunk in communicate.stream_sync(): if chunk['type'] == 'audio': audio_data += chunk['data'] return audio_data audio_data = asyncio.run(generate_audio()) # 返回音频数据(MP3格式) return Response(audio_data, mimetype='audio/mpeg') except Exception as e: return jsonify({'error': f'TTS生成失败: {str(e)}'}), 500 @app.route('/api/tts/voices', methods=['GET']) def get_tts_voices(): """获取可用的 TTS 语音列表""" try: voices = asyncio.run(edge_tts.list_voices()) # 过滤中文语音 chinese_voices = [v for v in voices if v['Locale'].startswith('zh-')] voice_list = [{ 'name': v['ShortName'], 'gender': v['Gender'], 'locale': v['Locale'] } for v in chinese_voices] return jsonify({'voices': voice_list}) except Exception as e: return jsonify({'error': f'获取语音列表失败: {str(e)}'}), 500 # ==================== 启动 ==================== if __name__ == '__main__': # 初始化数据库 init_db() # 启动服务 print('🚀 AI Chat App Backend started on http://localhost:19021') print('📊 Admin Panel: http://localhost:19021/admin') print('👤 Default admin: admin / admin123') app.run(host='0.0.0.0', port=19021, debug=True)