重构了main.html的主函数

重构了大量的页面的sidebar
优化了代码结构
This commit is contained in:
2026-02-02 09:22:52 +08:00
33 changed files with 5566 additions and 2383 deletions

View File

@@ -37,7 +37,25 @@ CONFIG = load_config()
TRAINING_LOGS_DIR = CONFIG.get('training_logs_path', '/app/base/training_logs')
# ============ 日志系统配置 ============
LOG_BASE_DIR = os.path.join(PROJECT_ROOT, 'logs')
# 日志目录逻辑:
# 1. 优先使用环境变量 LOG_BASE_DIR
# 2. 如果是容器环境(存在 /app/base使用 /app/base/logs
# 3. 否则使用本地项目路径 PROJECT_ROOT/logs
def get_log_base_dir():
"""获取日志基础目录"""
# 1. 检查环境变量
if 'LOG_BASE_DIR' in os.environ:
return os.environ['LOG_BASE_DIR']
# 2. 检查是否在容器环境中
mount_base = os.environ.get('MOUNT_BASE', '/app/base')
if os.path.exists(mount_base):
return os.path.join(mount_base, 'logs')
# 3. 使用本地项目路径
return os.path.join(PROJECT_ROOT, 'logs')
LOG_BASE_DIR = get_log_base_dir()
def setup_logger(name='app'):
@@ -98,6 +116,15 @@ def setup_logger(name='app'):
datefmt='%Y-%m-%d %H:%M:%S'
))
# 6. API日志处理器 - 专门记录API相关日志
api_log_path = os.path.join(log_dir, 'api.log')
api_handler = RotatingFileHandler(api_log_path, maxBytes=10*1024*1024, backupCount=10, encoding='utf-8')
api_handler.setLevel(logging.DEBUG)
api_handler.setFormatter(logging.Formatter(
'[%(asctime)s] %(levelname)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
))
# 添加处理器到 logger
logger.addHandler(all_handler)
logger.addHandler(error_handler)
@@ -117,6 +144,13 @@ def setup_logger(name='app'):
train_logger.addHandler(train_handler)
train_logger.addHandler(console_handler)
# 为 logs_api 创建单独的 logger (供 src/api/logs.py 使用)
logs_api_logger = logging.getLogger('logs_api')
logs_api_logger.setLevel(logging.DEBUG)
logs_api_logger.handlers.clear()
logs_api_logger.addHandler(api_handler)
logs_api_logger.addHandler(console_handler)
return logger
@@ -588,6 +622,8 @@ def system_info():
# ============ 通用 CRUD 操作 ============
import json
def generic_get_all(table_name, order_by='create_time DESC'):
"""通用查询所有"""
conn = get_db_connection()
@@ -596,6 +632,19 @@ def generic_get_all(table_name, order_by='create_time DESC'):
result = cursor.fetchall()
cursor.close()
conn.close()
# 自动解析 JSON 字段
for row in result:
for key, value in row.items():
if isinstance(value, str) and value.startswith('[') and value.endswith(']'):
try:
row[key] = json.loads(value)
except:
pass
elif isinstance(value, str) and value.startswith('{') and value.endswith('}'):
try:
row[key] = json.loads(value)
except:
pass
return result
@@ -607,6 +656,19 @@ def generic_get_by_id(table_name, id_val):
result = cursor.fetchone()
cursor.close()
conn.close()
# 自动解析 JSON 字段
if result:
for key, value in result.items():
if isinstance(value, str) and value.startswith('[') and value.endswith(']'):
try:
result[key] = json.loads(value)
except:
pass
elif isinstance(value, str) and value.startswith('{') and value.endswith('}'):
try:
result[key] = json.loads(value)
except:
pass
return result
@@ -1053,36 +1115,19 @@ def delete_model_deploy(id):
return jsonify({'code': 0, 'message': '删除成功'})
# ============ 模型管理接口 ============
@app.route('/api/model-manage', methods=['GET'])
def get_model_manage():
return jsonify({'code': 0, 'data': generic_get_all('model_manage')})
@app.route('/api/model-manage', methods=['POST'])
def create_model_manage():
data = request.json
new_id = generic_create('model_manage', data)
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
@app.route('/api/model-manage/<int:id>', methods=['PUT'])
def update_model_manage(id):
data = request.json
generic_update('model_manage', id, data)
return jsonify({'code': 0, 'message': '更新成功'})
@app.route('/api/model-manage/<int:id>', methods=['DELETE'])
def delete_model_manage(id):
generic_delete('model_manage', id)
return jsonify({'code': 0, 'message': '删除成功'})
# ============ 模型对比接口 ============
@app.route('/api/model-compare', methods=['GET'])
def get_model_compare():
return jsonify({'code': 0, 'data': generic_get_all('model_compare')})
result = generic_get_all('model_compare')
# 确保 models 字段被正确解析为 JSON
for row in result:
if 'models' in row and isinstance(row['models'], str):
try:
row['models'] = json.loads(row['models'])
logger.debug(f"[model-compare] 解析 models 字段成功: {row['models']}")
except Exception as e:
logger.error(f"[model-compare] 解析 models 字段失败: {e}, 原始值: {row['models']}")
return jsonify({'code': 0, 'data': result})
@app.route('/api/model-compare/<int:id>', methods=['GET'])
@@ -1090,13 +1135,25 @@ def get_model_compare_by_id(id):
"""获取单个模型对比任务"""
result = generic_get_by_id('model_compare', id)
if result:
# 确保 models 字段被正确解析为 JSON
if 'models' in result and isinstance(result['models'], str):
try:
result['models'] = json.loads(result['models'])
except Exception as e:
logger.error(f"[model-compare] 解析 models 字段失败: {e}")
return jsonify({'code': 0, 'data': result})
return jsonify({'code': 1, 'message': '任务不存在'})
@app.route('/api/model-compare', methods=['POST'])
def create_model_compare():
data = request.json
data = request.json.copy()
# 字段映射: name -> model_name
if 'name' in data:
data['model_name'] = data.pop('name')
# 设置默认加载状态
if 'status' not in data:
data['status'] = 'pending'
new_id = generic_create('model_compare', data)
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
@@ -1110,10 +1167,278 @@ def update_model_compare(id):
@app.route('/api/model-compare/<int:id>', methods=['DELETE'])
def delete_model_compare(id):
# 先停止加载的模型服务
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT models, load_status FROM model_compare WHERE id = %s", (id,))
row = cursor.fetchone()
cursor.close()
conn.close()
if row and row[1] == 'loaded':
# 停止模型服务
from subprocess import Popen
import signal
load_status = json.loads(row[0]) if isinstance(row[0], str) else row[0]
if isinstance(load_status, dict) and 'processes' in load_status:
for proc in load_status['processes']:
if 'pid' in proc:
try:
os.kill(proc['pid'], signal.SIGTERM)
except:
pass
except Exception as e:
logger.error(f"[model-compare] 停止模型服务失败: {e}")
generic_delete('model_compare', id)
return jsonify({'code': 0, 'message': '删除成功'})
# ============ 模型加载接口 ============
import subprocess
import signal
# 存储加载状态 (生产环境应使用数据库或Redis)
model_compare_processes = {}
@app.route('/api/model-compare/<int:id>/load', methods=['POST'])
def load_model_compare(id):
"""加载模型 - 启动模型服务"""
try:
# 获取对比任务
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT models FROM model_compare WHERE id = %s", (id,))
row = cursor.fetchone()
cursor.close()
conn.close()
if not row:
return jsonify({'code': 1, 'message': '任务不存在'})
models = json.loads(row[0]) if isinstance(row[0], str) else row[0]
if not models or len(models) < 2:
return jsonify({'code': 1, 'message': '模型配置无效'})
# 更新状态为 loading
generic_update('model_compare', id, {'status': 'loading'})
# 启动模型服务
processes = []
loaded_models = []
for i, model in enumerate(models):
model_path = model.get('model_path', '')
gpu_id = model.get('gpu_id', 0)
port = model.get('port', 7862 + i * 10)
if not model_path:
continue
# 构建启动命令
# 获取模型名称和模板
model_name = model.get('model_name', '')
template = detect_template(model_path)
cmd = [
sys.executable,
'-m', 'llamafactory.api',
'--model_name_or_path', model_path,
'--template', template or 'default',
'--port', str(port),
'--gpu_ids', str(gpu_id)
]
logger.info(f"[model-compare] 启动模型服务: {' '.join(cmd)}")
# 启动进程
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=PROJECT_ROOT
)
processes.append({
'pid': proc.pid,
'port': port,
'model_name': model_name,
'model_path': model_path
})
loaded_models.append({
'port': port,
'model_name': model_name,
'status': 'starting'
})
# 保存进程信息
model_compare_processes[id] = {
'processes': processes,
'loaded_models': loaded_models,
'start_time': datetime.now().timestamp()
}
# 更新数据库中的加载状态
load_status = {
'processes': processes,
'loaded_models': loaded_models,
'started_at': datetime.now().isoformat()
}
generic_update('model_compare', id, {
'status': 'loading',
'load_status': json.dumps(load_status, ensure_ascii=False)
})
return jsonify({
'code': 0,
'message': '正在加载模型...',
'data': {
'models': loaded_models,
'check_url': f'/api/model-compare/{id}/load-status'
}
})
except Exception as e:
logger.error(f"[model-compare] 加载模型失败: {e}")
generic_update('model_compare', id, {'status': 'pending'})
return jsonify({'code': 1, 'message': f'加载失败: {str(e)}'})
def detect_template(model_path):
"""根据模型路径检测模板类型"""
model_lower = model_path.lower()
if 'qwen' in model_lower:
return 'qwen'
elif 'llama' in model_lower or 'llama' in model_lower:
return 'llama'
elif 'chatglm' in model_lower:
return 'chatglm'
elif 'baichuan' in model_lower:
return 'baichuan'
elif 'mistral' in model_lower:
return 'mistral'
elif 'yi' in model_lower:
return 'yi'
elif 'deepseek' in model_lower:
return 'deepseek'
return 'default'
@app.route('/api/model-compare/<int:id>/load-status', methods=['GET'])
def get_load_status(id):
"""获取模型加载状态"""
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT models, load_status FROM model_compare WHERE id = %s", (id,))
row = cursor.fetchone()
cursor.close()
conn.close()
if not row:
return jsonify({'code': 1, 'message': '任务不存在'})
models = json.loads(row[0]) if isinstance(row[0], str) else row[0]
load_status = json.loads(row[1]) if row[1] and isinstance(row[1], str) else {}
loaded_models = load_status.get('loaded_models', [])
processes = load_status.get('processes', [])
# 检查每个模型服务是否就绪
all_ready = True
for i, model in enumerate(loaded_models):
port = model.get('port')
try:
import requests
resp = requests.get(f'http://localhost:{port}/health', timeout=2)
if resp.status_code == 200:
model['status'] = 'ready'
else:
model['status'] = 'starting'
all_ready = False
except:
# 检查进程是否还在运行
if 'pid' in processes[i] if i < len(processes) else False:
model['status'] = 'starting'
all_ready = False
else:
model['status'] = 'failed'
all_ready = False
# 更新状态
if all_ready:
generic_update('model_compare', id, {'status': 'loaded'})
else:
generic_update('model_compare', id, {'status': 'loading'})
# 更新加载状态
load_status['loaded_models'] = loaded_models
load_status['all_ready'] = all_ready
generic_update('model_compare', id, {
'load_status': json.dumps(load_status, ensure_ascii=False)
})
return jsonify({
'code': 0,
'data': {
'status': 'loaded' if all_ready else 'loading',
'models': loaded_models,
'all_ready': all_ready
}
})
except Exception as e:
logger.error(f"[model-compare] 获取加载状态失败: {e}")
return jsonify({'code': 1, 'message': str(e)})
@app.route('/api/model-compare/<int:id>/unload', methods=['POST'])
def unload_model_compare(id):
"""停止加载的模型服务"""
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT load_status FROM model_compare WHERE id = %s", (id,))
row = cursor.fetchone()
cursor.close()
conn.close()
if row and row[0]:
load_status = json.loads(row[0]) if isinstance(row[0], str) else row[0]
processes = load_status.get('processes', [])
# 停止所有进程
for proc in processes:
pid = proc.get('pid')
if pid:
try:
os.kill(pid, signal.SIGTERM)
logger.info(f"[model-compare] 已停止进程 {pid}")
except ProcessLookupError:
pass
except Exception as e:
logger.warning(f"[model-compare] 停止进程 {pid} 失败: {e}")
# 清理内存
if id in model_compare_processes:
del model_compare_processes[id]
# 更新状态
generic_update('model_compare', id, {
'status': 'pending',
'load_status': None
})
return jsonify({'code': 0, 'message': '已停止模型服务'})
except Exception as e:
logger.error(f"[model-compare] 停止模型服务失败: {e}")
return jsonify({'code': 1, 'message': str(e)})
# ============ 数据生成接口 ============
@app.route('/api/data-generate', methods=['GET'])
def get_data_generate():