1391 lines
49 KiB
Python
1391 lines
49 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
AI Chat App - 后台管理服务
|
||
端口: 19021 (与前端同一端口)
|
||
"""
|
||
|
||
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
|
||
import base64
|
||
|
||
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', '', '用户协议链接'),
|
||
]
|
||
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/<path:path>')
|
||
def admin_static(path):
|
||
"""后台管理静态文件"""
|
||
return send_from_directory(app.static_folder, path)
|
||
|
||
|
||
@app.route('/<path:path>')
|
||
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/<int:id>', 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/<int:id>', 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/<int:id>', 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/<int:id>/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/<int:id>', 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/<int:id>/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/<int:user_id>/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/<filename>', methods=['GET'])
|
||
def get_avatar(filename):
|
||
"""获取头像图片"""
|
||
return send_from_directory(AVATAR_DIR, filename)
|
||
|
||
|
||
@app.route('/api/admin/users/<int:user_id>/avatar', methods=['POST'])
|
||
def admin_upload_user_avatar(user_id):
|
||
"""管理员上传用户头像"""
|
||
return upload_user_avatar(user_id)
|
||
|
||
|
||
# ==================== 用户对话数据同步 ====================
|
||
|
||
@app.route('/api/user/<int:user_id>/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/<int:user_id>/conversations/<int:conv_id>', 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/<int:user_id>/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/<int:user_id>/conversations/<int:conv_id>', 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/<int:user_id>/conversations/<int:conv_id>', 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/<int:user_id>/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/<int:user_id>/agents/<agent_id>', 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/<int:user_id>/agents/<agent_id>', 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/<int:user_id>/agents/<agent_id>/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/<int:user_id>/agents/<agent_id>/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/<int:id>', 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/<int:id>', 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/<int:id>/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/<agent_id>', 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/<agent_id>', 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/<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'])
|
||
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/<tool_id>', 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/<tool_id>', 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/<tool_id>/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', ''),
|
||
}
|
||
}
|
||
|
||
return jsonify(config)
|
||
|
||
|
||
# ==================== 启动 ====================
|
||
|
||
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) |