diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0f05043..3f70db4 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -34,7 +34,8 @@ "Bash(./test_all.sh)", "Bash(/data/code/FT_Platform/YG_FT_Platform/test_data_dir.sh:*)", "Bash(grep:*)", - "Bash(mysql:*)" + "Bash(mysql:*)", + "Bash(ls:*)" ] } } diff --git a/src/main.py b/src/main.py index 0bdc417..838479e 100644 --- a/src/main.py +++ b/src/main.py @@ -103,6 +103,17 @@ def init_database(): update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4""", + # 模型对比表 + """CREATE TABLE IF NOT EXISTS model_compare ( + id INT AUTO_INCREMENT PRIMARY KEY, + model_name VARCHAR(255) NOT NULL, + description TEXT, + models JSON, + status VARCHAR(50) DEFAULT 'pending', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4""", + # 数据集管理表 """CREATE TABLE IF NOT EXISTS dataset_manage ( id INT AUTO_INCREMENT PRIMARY KEY, @@ -248,12 +259,20 @@ def generic_get_by_id(table_name, id_val): def generic_create(table_name, data): """通用创建""" + import json conn = get_db_connection() cursor = conn.cursor() + # 处理JSON字段,将列表和字典转为JSON字符串 + processed_values = [] + for value in data.values(): + if isinstance(value, (list, dict)): + processed_values.append(json.dumps(value, ensure_ascii=False)) + else: + processed_values.append(value) columns = ', '.join(data.keys()) placeholders = ', '.join(['%s'] * len(data)) sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})" - cursor.execute(sql, list(data.values())) + cursor.execute(sql, processed_values) conn.commit() new_id = cursor.lastrowid cursor.close() @@ -263,11 +282,19 @@ def generic_create(table_name, data): def generic_update(table_name, id_val, data): """通用更新""" + import json conn = get_db_connection() cursor = conn.cursor() + # 处理JSON字段,将列表和字典转为JSON字符串 + processed_values = [] + for value in data.values(): + if isinstance(value, (list, dict)): + processed_values.append(json.dumps(value, ensure_ascii=False)) + else: + processed_values.append(value) 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] + values = processed_values + [id_val] cursor.execute(sql, values) conn.commit() cursor.close() @@ -407,6 +434,32 @@ def delete_model_deploy(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')}) + + +@app.route('/api/model-compare', methods=['POST']) +def create_model_compare(): + data = request.json + new_id = generic_create('model_compare', data) + return jsonify({'code': 0, 'message': '创建成功', 'id': new_id}) + + +@app.route('/api/model-compare/', methods=['PUT']) +def update_model_compare(id): + data = request.json + generic_update('model_compare', id, data) + return jsonify({'code': 0, 'message': '更新成功'}) + + +@app.route('/api/model-compare/', methods=['DELETE']) +def delete_model_compare(id): + generic_delete('model_compare', id) + return jsonify({'code': 0, 'message': '删除成功'}) + + # ============ 数据生成接口 ============ @app.route('/api/data-generate', methods=['GET']) def get_data_generate(): diff --git a/web/pages/main.html b/web/pages/main.html index 6053d53..645806c 100644 --- a/web/pages/main.html +++ b/web/pages/main.html @@ -166,9 +166,9 @@ 模型评测 - + - 模型部署 + 模型对比 @@ -290,19 +290,25 @@ ], actions: ['report', 'delete'] }, - 'model-deploy': { + 'model-compare': { title: '模型对比', - api: 'model-deploy', + api: 'model-compare', hasCreate: true, createText: '新建对比', columns: [ - { title: '服务名称', key: 'model_name' }, - { title: '端点', key: 'endpoint' }, - { title: '实例', key: 'instance' }, + { title: '对比名称', key: 'model_name' }, + { title: '描述', key: 'description', render: (val) => val || '-' }, + { title: '模型列表', key: 'models', render: (val) => { + if (!val) return '-'; + try { + const models = typeof val === 'string' ? JSON.parse(val) : val; + return models.length + '个模型'; + } catch { return '-'; } + }}, { title: '状态', key: 'status', render: (val) => `${val}` }, { title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' } ], - actions: ['stop', 'scale', 'delete'] + actions: ['delete'] }, 'dataset-manage': { title: '数据集管理', @@ -1364,8 +1370,8 @@ window.location.href = 'model-eval-create.html'; } else if (apiType === 'dataset-manage') { window.location.href = 'dataset-create.html'; - } else if (apiType === 'model-deploy') { - window.location.href = 'model-deploy-create.html'; + } else if (apiType === 'model-compare') { + window.location.href = 'model-compare-create.html'; } else { showMessage('提示', '该功能开发中...', 'info'); } diff --git a/web/pages/model-deploy-create.html b/web/pages/model-compare-create.html similarity index 74% rename from web/pages/model-deploy-create.html rename to web/pages/model-compare-create.html index 348e1b8..338021b 100644 --- a/web/pages/model-deploy-create.html +++ b/web/pages/model-compare-create.html @@ -87,7 +87,7 @@ 模型评测 - + 模型对比 @@ -154,7 +154,7 @@
- 模型对比 + 模型对比 / 新建对比
@@ -182,12 +182,19 @@

选择对比模型 *

- 已选择 0 / 4 个模型 - 最多选择4个模型进行对比 +
+ 已选择 0 / 4 个模型 + 最多选择4个模型进行对比 +
+
+ + +
- -
-
+ +
+

加载模型列表中...

@@ -241,7 +248,7 @@ // 返回列表页 function goBack() { - window.location.href = 'main.html?page=model-deploy'; + window.location.href = 'main.html?page=model-compare'; } // 加载模型列表 @@ -271,7 +278,7 @@ const container = document.getElementById('modelList'); if (allModels.length === 0) { container.innerHTML = ` -
+

暂无模型,请先添加模型

@@ -291,21 +298,23 @@ }[model.type] || model.type || '其他'; return ` -
-
-
-
- ${model.name} - ${typeText} -
-

${model.description || '暂无描述'}

-

${model.model_source === 'local' ? '本地模型' : '在线模型'} | ${model.create_time ? new Date(model.create_time).toLocaleDateString('zh-CN') : '-'}

-
-
-
+
+
+
${isSelected ? '' : ''}
+
+
+ ${model.name} + ${typeText} +
+

${model.description || '暂无描述'}

+
+
+
+ ${model.model_source === 'local' ? '本地模型' : '在线模型'}
@@ -328,6 +337,67 @@ renderModels(); } + // 筛选模型 + function filterModels(keyword) { + const filtered = allModels.filter(model => { + const name = model.name?.toLowerCase() || ''; + const desc = model.description?.toLowerCase() || ''; + const type = model.type?.toLowerCase() || ''; + const kw = keyword.toLowerCase(); + return name.includes(kw) || desc.includes(kw) || type.includes(kw); + }); + renderFilteredModels(filtered); + } + + // 渲染筛选后的模型列表 + function renderFilteredModels(models) { + const container = document.getElementById('modelList'); + if (models.length === 0) { + container.innerHTML = ` +
+ +

未找到匹配的模型

+
+ `; + return; + } + + container.innerHTML = models.map(model => { + const isSelected = selectedModels.has(model.id); + const isDisabled = !isSelected && selectedModels.size >= 4; + const typeText = { + 'LLM': '大语言模型', + 'CV': '计算机视觉', + 'NLP': '自然语言处理', + 'Embedding': '向量模型', + 'Other': '其他' + }[model.type] || model.type || '其他'; + + return ` +
+
+
+
+ ${isSelected ? '' : ''} +
+
+
+ ${model.name} + ${typeText} +
+

${model.description || '暂无描述'}

+
+
+
+ ${model.model_source === 'local' ? '本地模型' : '在线模型'} +
+
+
+ `; + }).join(''); + } + // 更新已选数量 function updateSelectedCount() { document.getElementById('selectedCount').textContent = selectedModels.size; @@ -392,15 +462,15 @@ } const data = { - name: name, + model_name: name, description: description, models: Array.from(selectedModels), status: 'pending', - create_time: new Date().toISOString() + create_time: new Date().toLocaleString('zh-CN', { hour12: false }).replace(/\//g, '-') }; try { - const response = await fetch(`${API_BASE}/model-deploy`, { + const response = await fetch(`${API_BASE}/model-compare`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) @@ -436,6 +506,19 @@ }); } + // 绑定模型搜索框事件 + const modelSearchInput = document.getElementById('modelSearchInput'); + if (modelSearchInput) { + modelSearchInput.addEventListener('input', (e) => { + const keyword = e.target.value.trim(); + if (keyword) { + filterModels(keyword); + } else { + renderModels(); + } + }); + } + // 加载模型列表 loadModels(); });