feat: 后台管理添加用户管理功能 + 用户注册登录改为调用backend API
This commit is contained in:
17
MEMORY.md
17
MEMORY.md
@@ -166,6 +166,23 @@ server.quit()
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 2026-04-27
|
||||
- **AI Chat App v3.6.3**:修复智能体历史使用记录逻辑
|
||||
- 只显示真正有对话消息的智能体记录(messages.length > 0)
|
||||
- 修复点击打开智能体就记录"使用"的问题
|
||||
- Git仓库: http://192.168.2.8:12007/coder/ai-chat-app v3.6.3
|
||||
|
||||
### 2026-04-26
|
||||
- **AI对话系统 v3.1.0**:UI重构 - 历史对话列表页
|
||||
- 首页改为历史对话列表页(而非侧边栏)
|
||||
- 新建对话按钮放在右上角
|
||||
- 历史对话列表支持删除操作
|
||||
- 点击对话进入具体对话页面
|
||||
- 对话页面有返回按钮回到列表
|
||||
- API新增 message_count 字段
|
||||
- API新增 GET /api/conversations/{id} 获取详情
|
||||
- Git仓库: http://192.168.2.8:12007/coder/ai-chat-system v3.1.0
|
||||
|
||||
### 2026-04-24
|
||||
- **Xian Favor v4.6.3**:修复Markdown显示开关切换失效
|
||||
- 改用 data-raw-content 属性存储原始内容
|
||||
|
||||
33
memory/2026-04-26.md
Normal file
33
memory/2026-04-26.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 2026-04-26 每日事务记录
|
||||
|
||||
## 开发任务
|
||||
|
||||
### AI对话系统 v3.1.0 - UI重构:历史对话列表页
|
||||
|
||||
**需求来源**: 优化用户体验,改进对话管理方式
|
||||
|
||||
**主要改动**:
|
||||
1. 首页改为历史对话列表页(而非侧边栏)
|
||||
2. 新建对话按钮放在右上角
|
||||
3. 历史对话列表支持删除操作
|
||||
4. 点击对话进入具体对话页面
|
||||
5. 对话页面有返回按钮回到列表
|
||||
6. API新增 message_count 字段
|
||||
7. API新增 GET /api/conversations/{id} 获取详情
|
||||
|
||||
**Git仓库**: http://192.168.2.8:12007/coder/ai-chat-system v3.1.0
|
||||
|
||||
---
|
||||
|
||||
## 项目信息
|
||||
|
||||
- 项目名: AI对话系统
|
||||
- 目录: `works/ai-chat-system`
|
||||
- 端口: 19020
|
||||
- Git仓库: http://192.168.2.8:12007/coder/ai-chat-system
|
||||
|
||||
---
|
||||
|
||||
## 备注
|
||||
|
||||
今日只记录了 AI对话系统 v3.1.0 一个版本更新。如有其他事务需要补充,请告知。
|
||||
201
memory/2026-04-27.md
Normal file
201
memory/2026-04-27.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# 2026-04-27 每日事务记录
|
||||
|
||||
## 开发任务
|
||||
|
||||
### AI Chat App v3.2.0 - 智能体发现和收藏功能
|
||||
|
||||
**需求来源**: 用户(huangzhuang_bro)
|
||||
|
||||
**主要改动**:
|
||||
1. 智能体界面顶部右侧添加搜索按钮(发现智能体)
|
||||
2. 点击进入发现页面,显示所有系统智能体
|
||||
3. 每个智能体显示简介、热度、类别
|
||||
4. 支持添加和收藏按钮
|
||||
5. 添加的智能体自动归类到对应类别
|
||||
6. 智能体界面顶部右侧添加收藏按钮(☆)
|
||||
7. 点击进入收藏夹页面,点击智能体进入对话
|
||||
8. 智能体长按弹出操作菜单(置顶、收藏、删除)
|
||||
9. 置顶、收藏、用户智能体配置持久化存储
|
||||
|
||||
**Git仓库**: http://192.168.2.8:12007/coder/ai-chat-app v3.2.0
|
||||
|
||||
---
|
||||
|
||||
### AI Chat App v3.2.1 - 智能体对话和历史对话分离
|
||||
|
||||
**需求来源**: 用户(huangzhuang_bro)
|
||||
|
||||
**主要改动**:
|
||||
1. 对话页面只显示没有智能体的普通对话
|
||||
2. 智能体对话只在智能体页面的最近使用列表显示
|
||||
3. 搜索功能也只搜索普通对话
|
||||
|
||||
**Git仓库**: http://192.168.2.8:12007/coder/ai-chat-app v3.2.1
|
||||
|
||||
---
|
||||
|
||||
### AI Chat App v3.3.0 - 登录注册系统 + 游客使用限制
|
||||
|
||||
**需求来源**: 用户(huangzhuang_bro)
|
||||
|
||||
**主要改动**:
|
||||
1. 登录/注册界面(本地存储用户数据)
|
||||
2. 我的页面显示用户状态、登录/退出按钮
|
||||
3. 游客配额显示(对话会话/消息/智能体消息)
|
||||
|
||||
**游客限制**:
|
||||
- 每天最多1个对话会话
|
||||
- 每天最多20条对话消息
|
||||
- 只能使用通用助手智能体
|
||||
- 每天最多20条智能体消息
|
||||
- 登录用户无限制
|
||||
|
||||
**限制提示**:
|
||||
- 达到限制弹出提示对话框
|
||||
- 提示登录解锁全部功能
|
||||
|
||||
**Git仓库**: http://192.168.2.8:12007/coder/ai-chat-app v3.3.0
|
||||
|
||||
---
|
||||
|
||||
## 项目信息
|
||||
|
||||
- 项目名: AI Chat App
|
||||
- 目录: `works/ai-chat-app`
|
||||
- Git仓库: http://192.168.2.8:12007/coder/ai-chat-app
|
||||
|
||||
---
|
||||
|
||||
### AI Chat App v3.4.0 - 注册增加手机号验证和邮箱
|
||||
|
||||
**需求来源**: 用户(huangzhuang_bro)
|
||||
|
||||
**主要改动**:
|
||||
1. 手机号输入框(必填)
|
||||
- 11位中国手机号格式验证(/^1[3-9]\d{9}$/)
|
||||
- 只允许输入数字
|
||||
- 手机号唯一性检查(防止重复注册)
|
||||
2. 验证码机制(模拟)
|
||||
- 点击"获取验证码"生成6位随机码
|
||||
- 验证码5分钟有效期
|
||||
- 发送按钮60秒倒计时
|
||||
- 验证码显示在toast中(演示用)
|
||||
3. 验证码输入框(必填)
|
||||
4. 邮箱输入框(可选)
|
||||
- 格式验证(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
|
||||
|
||||
**Git仓库**: http://192.168.2.8:12007/coder/ai-chat-app v3.4.1
|
||||
|
||||
---
|
||||
|
||||
### AI Chat App v3.5.0 - 我的界面功能完善
|
||||
|
||||
**需求来源**: 用户(huangzhuang_bro)
|
||||
|
||||
**主要改动**:
|
||||
1. 用户编辑页面
|
||||
- 用户头像选择(15种预设头像)
|
||||
- 用户名修改(唯一性检查)
|
||||
- 个性签名(最多50字符)
|
||||
- 个人资料:手机号(只读)、邮箱、性别、年龄、地区
|
||||
|
||||
2. 消息通知页面
|
||||
- 通知开关设置
|
||||
- 通知声音开关
|
||||
- 震动提醒开关
|
||||
- 免打扰时段设置
|
||||
|
||||
3. 隐私安全页面
|
||||
- 修改密码
|
||||
- 账号信息查看(注册时间等)
|
||||
- 清除聊天记录
|
||||
- 清除缓存
|
||||
- 数据安全状态显示
|
||||
|
||||
4. 帮助与反馈页面
|
||||
- 使用帮助指南(4个常见问题)
|
||||
- 问题反馈表单
|
||||
- 联系方式
|
||||
|
||||
5. 关于页面
|
||||
- 版本信息、开发者、更新日期
|
||||
- 检查更新
|
||||
- 隐私政策、用户协议入口
|
||||
|
||||
**Git仓库**: http://192.168.2.8:12007/coder/ai-chat-app v3.5.1
|
||||
|
||||
---
|
||||
|
||||
### AI Chat App v3.6.0 - 后台管理系统
|
||||
|
||||
**需求来源**: 用户(huangzhuang_bro)
|
||||
|
||||
**主要改动**:
|
||||
1. Python Flask 后端服务(端口 19020)
|
||||
- 前端和后台管理共用同一端口
|
||||
- SQLite 数据库存储配置
|
||||
|
||||
2. 后台管理功能
|
||||
- 大模型接口配置管理:增删改、设为默认
|
||||
- 智能体管理:增删改(类别、标签、prompt、LLM配置、热度、联网搜索开关)
|
||||
- 搜索配置管理:Tavily等搜索接口
|
||||
- 系统设置:应用名称、版本、游客限制、管理员密码
|
||||
- 统计信息:LLM调用、搜索调用、智能体使用排行
|
||||
|
||||
3. 后台入口
|
||||
- URL: http://localhost:19020/admin
|
||||
- 默认账号: admin / admin123
|
||||
|
||||
4. 前端配置获取
|
||||
- /api/config:获取LLM、搜索、智能体、系统配置
|
||||
- 前端可根据后端配置动态调整
|
||||
|
||||
**启动命令**:
|
||||
```bash
|
||||
cd works/ai-chat-app/backend
|
||||
python3 app.py
|
||||
```
|
||||
|
||||
**Git仓库**: http://192.168.2.8:12007/coder/ai-chat-app v3.6.1
|
||||
|
||||
---
|
||||
|
||||
### AI Chat App v3.6.2 - 对话配置扁平化
|
||||
|
||||
**需求来源**: 用户(huangzhuang_bro)
|
||||
|
||||
**主要改动**:
|
||||
1. 对话配置改为扁平化单配置
|
||||
- 只有一个默认配置,所有用户共用
|
||||
- 去掉添加/删除配置按钮和逻辑
|
||||
- 直接编辑保存
|
||||
|
||||
2. 配置参数扁平化
|
||||
- enable_search 启用联网搜索开关
|
||||
- LLM配置、可用工具
|
||||
- 历史记录数、Temperature、系统提示词
|
||||
|
||||
3. 启用联网搜索开关
|
||||
- 从系统设置移到对话配置页面
|
||||
- 开关自动添加/移除 search 工具
|
||||
|
||||
**Git仓库**: http://192.168.2.8:12007/coder/ai-chat-app v3.6.2
|
||||
|
||||
---
|
||||
|
||||
## 备注
|
||||
|
||||
今日完成了四个版本的 AI Chat App 更新,主要增加了智能体管理功能、登录系统和手机号验证。
|
||||
|
||||
---
|
||||
|
||||
### AI Chat App v3.6.3 - 修复智能体历史使用记录逻辑
|
||||
|
||||
**需求来源**: 用户(huangzhuang_bro)
|
||||
|
||||
**主要改动**:
|
||||
1. `getAgentConversationHistory` 函数增加消息数量筛选
|
||||
2. 只返回 `messages.length > 0` 的对话记录
|
||||
3. 修复点击打开智能体就被记录为"使用"的问题
|
||||
|
||||
**Git仓库**: http://192.168.2.8:12007/coder/ai-chat-app v3.6.3
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"lastSummaryDate": "2026-04-21",
|
||||
"lastSummaryDate": "2026-04-27",
|
||||
"lastChecks": {
|
||||
"memory_created": "2026-04-21T02:24:00Z"
|
||||
"memory_created": "2026-04-21T02:24:00Z",
|
||||
"2026-04-26_summary": "已整理并创建 memory/2026-04-26.md"
|
||||
}
|
||||
}
|
||||
Submodule works/ai-chat updated: daccc625c3...26a76b030d
12
works/ai-chat-app/.gitignore
vendored
Normal file
12
works/ai-chat-app/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
*.so
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
*.db
|
||||
data.db
|
||||
node_modules/
|
||||
997
works/ai-chat-app/backend/app.py
Normal file
997
works/ai-chat-app/backend/app.py
Normal file
@@ -0,0 +1,997 @@
|
||||
#!/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_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('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.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/<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/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 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,
|
||||
'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.6.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')),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
2
works/ai-chat-app/backend/requirements.txt
Normal file
2
works/ai-chat-app/backend/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
flask>=2.0.0
|
||||
flask-cors>=3.0.0
|
||||
4
works/ai-chat-app/backend/start.sh
Executable file
4
works/ai-chat-app/backend/start.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
pip install -r requirements.txt -q
|
||||
python app.py
|
||||
482
works/ai-chat-app/www/admin.html
Normal file
482
works/ai-chat-app/www/admin.html
Normal file
@@ -0,0 +1,482 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI助手 - 后台管理</title>
|
||||
<link rel="stylesheet" href="admin.css?v=3.6.6">
|
||||
<style>
|
||||
:root {
|
||||
--primary: #667eea;
|
||||
--primary-dark: #5a67d8;
|
||||
--bg-color: #f5f7fa;
|
||||
--card-bg: #ffffff;
|
||||
--text-color: #2d3748;
|
||||
--text-light: #718096;
|
||||
--border-color: #e2e8f0;
|
||||
--danger: #e53e3e;
|
||||
--success: #22c55e;
|
||||
--warning: #f59e0b;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 登录页面 */
|
||||
.login-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 16px;
|
||||
width: 350px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.login-input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
margin-bottom: 16px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.login-input:focus {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.login-btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 主页面 */
|
||||
.admin-page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 24px;
|
||||
background: white;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.admin-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.admin-logo span {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.admin-header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.admin-logout {
|
||||
padding: 8px 16px;
|
||||
background: var(--danger);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 侧边栏 */
|
||||
.admin-container {
|
||||
display: flex;
|
||||
height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.admin-sidebar {
|
||||
width: 220px;
|
||||
background: white;
|
||||
border-right: 1px solid var(--border-color);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.sidebar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.sidebar-item:hover {
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
.sidebar-item.active {
|
||||
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidebar-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* 内容区 */
|
||||
.admin-content {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.content-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
padding: 10px 20px;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 数据卡片 */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
/* 数据表格 */
|
||||
.data-table {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.data-table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.data-table th, .data-table td {
|
||||
padding: 14px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background: var(--bg-color);
|
||||
font-weight: 600;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.data-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.data-table tr:hover td {
|
||||
background: rgba(102, 126, 234, 0.05);
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-btns {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.action-btn.edit {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.delete {
|
||||
background: var(--danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.default {
|
||||
background: var(--success);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 默认标记 */
|
||||
.default-badge {
|
||||
padding: 4px 8px;
|
||||
background: var(--success);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 模态框 */
|
||||
.modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 16px;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-input, .form-select, .form-textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-input:focus, .form-select:focus, .form-textarea:focus {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.form-submit {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--text-color);
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.toast.show {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 登录页面 -->
|
||||
<div class="login-page" id="loginPage">
|
||||
<div class="login-card">
|
||||
<div class="login-logo">🤖</div>
|
||||
<h1 class="login-title">后台管理系统</h1>
|
||||
<input type="text" class="login-input" id="loginUsername" placeholder="用户名">
|
||||
<input type="password" class="login-input" id="loginPassword" placeholder="密码">
|
||||
<button class="login-btn" id="loginBtn">登录</button>
|
||||
<p style="margin-top: 16px; color: #999; font-size: 12px;">默认: admin / admin123</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 管理页面 -->
|
||||
<div class="admin-page" id="adminPage">
|
||||
<header class="admin-header">
|
||||
<div class="admin-logo">
|
||||
<span>🤖</span>
|
||||
AI助手 - 后台管理
|
||||
</div>
|
||||
<div class="admin-header-right">
|
||||
<span id="adminName">管理员</span>
|
||||
<button class="admin-logout" id="logoutBtn">退出</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="admin-container">
|
||||
<aside class="admin-sidebar">
|
||||
<div class="sidebar-item active" data-page="stats">
|
||||
<span class="sidebar-icon">📊</span>
|
||||
<span>统计信息</span>
|
||||
</div>
|
||||
<div class="sidebar-item" data-page="users">
|
||||
<span class="sidebar-icon">👥</span>
|
||||
<span>用户管理</span>
|
||||
</div>
|
||||
<div class="sidebar-item" data-page="llm">
|
||||
<span class="sidebar-icon">🧠</span>
|
||||
<span>大模型配置</span>
|
||||
</div>
|
||||
<div class="sidebar-item" data-page="agents">
|
||||
<span class="sidebar-icon">🤖</span>
|
||||
<span>智能体管理</span>
|
||||
</div>
|
||||
<div class="sidebar-item" data-page="chat">
|
||||
<span class="sidebar-icon">💬</span>
|
||||
<span>对话配置</span>
|
||||
</div>
|
||||
<div class="sidebar-item" data-page="tools">
|
||||
<span class="sidebar-icon">🔧</span>
|
||||
<span>工具配置</span>
|
||||
</div>
|
||||
<div class="sidebar-item" data-page="system">
|
||||
<span class="sidebar-icon">⚙️</span>
|
||||
<span>系统设置</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="admin-content" id="mainContent">
|
||||
<!-- 内容区动态加载 -->
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<!-- 模态框 -->
|
||||
<div class="modal" id="modal">
|
||||
<div class="modal-content" id="modalContent">
|
||||
<!-- 动态内容 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="admin.js?v=3.6.6"></script>
|
||||
</body>
|
||||
</html>
|
||||
1312
works/ai-chat-app/www/admin.js
Normal file
1312
works/ai-chat-app/www/admin.js
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,16 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="theme-color" content="#667eea">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>AI助手</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="style.css?v=3.5.1">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="marked.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
<script src="marked.min.js?v=3.5.1"></script>
|
||||
<script src="app.js?v=3.5.1"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user