From fa9c976f28938d6bdf7b3f3c10faa633247f3ad2 Mon Sep 17 00:00:00 2001 From: leokaka1 Date: Tue, 20 Jan 2026 16:16:13 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E5=A2=9E=E5=8A=A0=E4=BA=86=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2=E7=9A=84=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=202.=20=E4=BF=AE=E6=94=B9=E4=BA=86=E8=8B=A5=E5=B9=B2?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/__init__.py | 2 + src/api/model_manage.py | 145 +++++++++++++++++++++++++++++ src/main.py | 31 +----- web/pages/main.html | 126 +++++++++++++++++++++---- web/pages/model-manage-create.html | 115 +++++++++++++++++------ 5 files changed, 347 insertions(+), 72 deletions(-) create mode 100644 src/api/model_manage.py diff --git a/src/api/__init__.py b/src/api/__init__.py index b7e411a..84d6dc2 100644 --- a/src/api/__init__.py +++ b/src/api/__init__.py @@ -2,8 +2,10 @@ API 路由包 """ from .datasets import datasets_bp +from .model_manage import model_manage_bp # 注册所有蓝图 def register_blueprints(app): """注册所有蓝图""" app.register_blueprint(datasets_bp) + app.register_blueprint(model_manage_bp) diff --git a/src/api/model_manage.py b/src/api/model_manage.py new file mode 100644 index 0000000..6453052 --- /dev/null +++ b/src/api/model_manage.py @@ -0,0 +1,145 @@ +""" +模型管理 API 路由 +""" +import os +import pymysql +import yaml +from flask import Blueprint, request, jsonify + +# 获取项目根目录 +PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +# 创建蓝图 +model_manage_bp = Blueprint('model_manage', __name__, url_prefix='/api/model-manage') + + +def get_db_connection(): + """获取数据库连接""" + CONFIG_PATH = os.path.join(PROJECT_ROOT, 'config.yaml') + with open(CONFIG_PATH, 'r', encoding='utf-8') as f: + CONFIG = yaml.safe_load(f) + db_config = CONFIG['database'] + return pymysql.connect( + host=db_config['host'], + port=db_config['port'], + user=db_config['username'], + password=db_config['password'], + database=db_config['name'], + charset=db_config.get('charset', 'utf8mb4'), + cursorclass=pymysql.cursors.DictCursor + ) + + +def generic_get_all(table_name, order_by='create_time DESC'): + """通用查询所有""" + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute(f"SELECT * FROM {table_name} ORDER BY {order_by}") + result = cursor.fetchall() + cursor.close() + conn.close() + return result + + +def generic_create(table_name, data): + """通用创建""" + conn = get_db_connection() + cursor = conn.cursor() + columns = ', '.join(data.keys()) + placeholders = ', '.join(['%s'] * len(data)) + sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})" + cursor.execute(sql, list(data.values())) + conn.commit() + new_id = cursor.lastrowid + cursor.close() + conn.close() + return new_id + + +def generic_update(table_name, id_val, data): + """通用更新""" + conn = get_db_connection() + cursor = conn.cursor() + set_clause = ', '.join([f"{k} = %s" for k in data.keys()]) + sql = f"UPDATE {table_name} SET {set_clause} WHERE id = %s" + values = list(data.values()) + [id_val] + cursor.execute(sql, values) + conn.commit() + cursor.close() + conn.close() + + +def generic_delete(table_name, id_val): + """通用删除""" + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute(f"DELETE FROM {table_name} WHERE id = %s", (id_val,)) + conn.commit() + cursor.close() + conn.close() + + +def generic_get_by_id(table_name, id_val): + """通用按ID查询""" + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute(f"SELECT * FROM {table_name} WHERE id = %s", (id_val,)) + result = cursor.fetchone() + cursor.close() + conn.close() + return result + + +# ============ 模型管理 CRUD ============ + +@model_manage_bp.route('', methods=['GET']) +def get_model_manage(): + """获取所有模型""" + return jsonify({'code': 0, 'data': generic_get_all('model_manage')}) + + +@model_manage_bp.route('/', methods=['GET']) +def get_model_manage_by_id(id): + """获取单个模型""" + model = generic_get_by_id('model_manage', id) + if model: + return jsonify({'code': 0, 'data': model}) + return jsonify({'code': 1, 'message': '模型不存在'}) + + +@model_manage_bp.route('', methods=['POST']) +def create_model_manage(): + """创建模型""" + data = request.json + # 构建插入数据 + insert_data = { + 'name': data.get('name'), + 'type': data.get('type'), + 'model_source': data.get('model_source', 'local'), + 'description': data.get('description') + } + + if data.get('model_source') == 'local': + insert_data['path'] = data.get('path', '') + else: + insert_data['api_url'] = data.get('api_url', '') + insert_data['api_key'] = data.get('api_key', '') + insert_data['model_name'] = data.get('model_name', '') + + new_id = generic_create('model_manage', insert_data) + return jsonify({'code': 0, 'message': '创建成功', 'id': new_id}) + + +@model_manage_bp.route('/', methods=['PUT']) +def update_model_manage(id): + """更新模型""" + data = request.json + generic_update('model_manage', id, data) + return jsonify({'code': 0, 'message': '更新成功'}) + + +@model_manage_bp.route('/', methods=['DELETE']) +def delete_model_manage(id): + """删除模型""" + generic_delete('model_manage', id) + return jsonify({'code': 0, 'message': '删除成功'}) diff --git a/src/main.py b/src/main.py index 7156809..0bdc417 100644 --- a/src/main.py +++ b/src/main.py @@ -157,8 +157,11 @@ def init_database(): id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, type VARCHAR(100), - version VARCHAR(50), + model_source VARCHAR(20) DEFAULT 'local', path VARCHAR(500), + api_url VARCHAR(500), + api_key VARCHAR(500), + model_name VARCHAR(255), description TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP @@ -456,32 +459,6 @@ def delete_permission(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/', 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/', methods=['DELETE']) -def delete_model_manage(id): - generic_delete('model_manage', id) - return jsonify({'code': 0, 'message': '删除成功'}) - - # ============ 系统配置接口 ============ @app.route('/api/sys-config', methods=['GET']) def get_sys_config(): diff --git a/web/pages/main.html b/web/pages/main.html index 49bb7fd..50310b4 100644 --- a/web/pages/main.html +++ b/web/pages/main.html @@ -217,10 +217,12 @@
@@ -309,12 +311,24 @@ createText: '上传数据集', columns: [ { title: '数据集名称', key: 'name' }, - { title: '类型', key: 'type' }, + { title: '数据类型', key: 'type', render: (val) => { + const textMap = { + 'train': '训练数据', + 'test': '测试数据', + 'val': '验证数据', + 'other': '其他' + }; + const displayText = textMap[val?.toLowerCase()] || val || '-'; + return '' + displayText + ''; + }}, { title: '存储位置', key: 'storage_type', render: (val) => { - if (val === 'local') return '本地存储'; - if (val === 'minio') return 'MinIO'; - if (val === 'cloud') return '云存储'; - return '' + (val || '-') + ''; + const textMap = { + 'local': '本地存储', + 'minio': 'MinIO', + 'cloud': '云存储' + }; + const displayText = textMap[val] || val || '-'; + return '' + displayText + ''; }}, { title: '大小', key: 'size', render: (val) => (val && val !== '0 B' && val !== '0') ? val : '-' }, { title: '数据条数', key: 'count', render: (val) => val || 0 }, @@ -340,9 +354,25 @@ createText: '添加模型', columns: [ { title: '模型名称', key: 'name' }, - { title: '模型类型', key: 'type' }, - { title: '模型版本', key: 'version' }, - { title: '模型路径', key: 'path', render: (val) => val || '-' }, + { title: '模型类型', key: 'type', render: (val) => { + const textMap = { + 'LLM': '大语言模型', + 'CV': '计算机视觉', + 'NLP': '自然语言处理', + 'Embedding': '向量模型', + 'Other': '其他' + }; + const displayText = textMap[val] || val || '-'; + return '' + displayText + ''; + }}, + { title: '模型来源', key: 'model_source', render: (val) => { + const textMap = { + 'local': '本地模型', + 'online': '在线模型' + }; + const displayText = textMap[val] || val || '-'; + return '' + displayText + ''; + }}, { title: '描述', key: 'description', render: (val) => val || '-' }, { title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' } ], @@ -383,8 +413,30 @@ const urlParams = new URLSearchParams(window.location.search); const pageParam = urlParams.get('page'); - // 加载页面(优先使用URL参数) - const defaultPage = pageParam || 'fine-tune'; + // 确定加载的页面 + let defaultPage = 'fine-tune'; // 默认页面 + + if (pageParam) { + // URL有参数,优先使用URL参数 + defaultPage = pageParam; + } else { + // 没有URL参数,使用 sessionStorage 或 localStorage + // 优先使用 sessionStorage(仅当前会话有效) + const sessionPage = sessionStorage.getItem('lastPage'); + const localPage = localStorage.getItem('lastPage'); + const savedPage = sessionPage || localPage; + + if (savedPage && tableConfigs[savedPage]) { + defaultPage = savedPage; + } + // 否则保持默认 'fine-tune' + } + + // 保存到 sessionStorage(当前会话) + sessionStorage.setItem('lastPage', defaultPage); + + console.log('Page init - URL param:', pageParam, 'saved:', sessionStorage.getItem('lastPage'), 'loading:', defaultPage); + loadPage(defaultPage); // 更新侧边栏高亮状态 @@ -405,6 +457,10 @@ const page = this.dataset.page; loadPage(page); + // 保存当前页面到 sessionStorage(当前会话有效,关闭浏览器后失效) + sessionStorage.setItem('lastPage', page); + localStorage.setItem('lastPage', page); // 同时保存到 localStorage + // 更新高亮状态 document.querySelectorAll('.nav-link').forEach(l => { l.classList.remove('sidebar-item-active'); @@ -495,6 +551,9 @@ if (api === 'dataset-manage') { // 跳转到数据集创建页面进行编辑 window.location.href = `dataset-create.html?id=${id}`; + } else if (api === 'model-manage') { + // 跳转到模型创建页面进行编辑 + window.location.href = `model-manage-create.html?id=${id}`; } else { showMessage('提示', '编辑功能开发中...', 'info'); } @@ -508,6 +567,26 @@ window.open(`${baseUrl}/api/dataset-manage/download/${datasetId}`, '_blank'); } + // 筛选表格 + function filterTable() { + const searchInput = document.getElementById('tableSearchInput'); + if (!searchInput) return; + + const keyword = searchInput.value.toLowerCase().trim(); + const tbody = document.querySelector('#page-content table tbody'); + if (!tbody) return; + + const rows = tbody.querySelectorAll('tr'); + rows.forEach(row => { + const text = row.querySelector('td')?.textContent?.toLowerCase() || ''; + if (keyword === '' || text.includes(keyword)) { + row.style.display = ''; + } else { + row.style.display = 'none'; + } + }); + } + // 渲染表格页面 function renderTablePage(config, data) { const createButton = config.hasCreate ? ` @@ -516,6 +595,16 @@ ` : ''; + // 搜索框(模型管理和数据集管理) + const searchBox = (config.api === 'model-manage' || config.api === 'dataset-manage') ? ` +
+ + +
+ ` : ''; + const columns = config.columns; const hasData = data && data.length > 0; @@ -524,6 +613,7 @@

${config.title}

+ ${searchBox} ${createButton}
@@ -1079,8 +1169,12 @@ // 页面加载后初始化并启动定时器 document.addEventListener('DOMContentLoaded', function() { - initGPUList(); - startRefreshTimer(); + // 只在平台性能页面初始化GPU列表 + const gpuCountEl = document.getElementById('gpuCount'); + if (gpuCountEl) { + initGPUList(); + startRefreshTimer(); + } }); function saveConfig() { diff --git a/web/pages/model-manage-create.html b/web/pages/model-manage-create.html index 4e8a879..7cda7fd 100644 --- a/web/pages/model-manage-create.html +++ b/web/pages/model-manage-create.html @@ -178,8 +178,7 @@ *模型类型 -

建议使用语义化版本号,如:v1.0.0、v2.1.0

-
- - - -
-

模型路径

+

模型来源

-