1 .修改了新建评估指标删除的bug
This commit is contained in:
@@ -39,7 +39,9 @@
|
||||
"Bash(xargs:*)",
|
||||
"Bash(/root/miniconda3/bin/pip install psutil)",
|
||||
"Bash(python -m py_compile:*)",
|
||||
"Bash(git checkout:*)"
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(pip show:*)",
|
||||
"Bash(pip install:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ API 路由包
|
||||
from .datasets import datasets_bp
|
||||
from .model_manage import model_manage_bp
|
||||
from .model_chat import model_chat_bp
|
||||
from .dimension import dimension_bp
|
||||
|
||||
# 注册所有蓝图
|
||||
def register_blueprints(app):
|
||||
@@ -11,3 +12,4 @@ def register_blueprints(app):
|
||||
app.register_blueprint(datasets_bp)
|
||||
app.register_blueprint(model_manage_bp)
|
||||
app.register_blueprint(model_chat_bp)
|
||||
app.register_blueprint(dimension_bp)
|
||||
|
||||
144
src/api/dimension.py
Normal file
144
src/api/dimension.py
Normal file
@@ -0,0 +1,144 @@
|
||||
"""
|
||||
评测维度管理 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__))))
|
||||
|
||||
# 创建蓝图
|
||||
dimension_bp = Blueprint('dimension', __name__, url_prefix='/api/dimension')
|
||||
|
||||
|
||||
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 ============
|
||||
|
||||
@dimension_bp.route('', methods=['GET'])
|
||||
def get_dimensions():
|
||||
"""获取所有评测维度"""
|
||||
return jsonify({'code': 0, 'data': generic_get_all('model_dimension')})
|
||||
|
||||
|
||||
@dimension_bp.route('/<int:id>', methods=['GET'])
|
||||
def get_dimension_by_id(id):
|
||||
"""获取单个评测维度"""
|
||||
dimension = generic_get_by_id('model_dimension', id)
|
||||
if dimension:
|
||||
return jsonify({'code': 0, 'data': dimension})
|
||||
return jsonify({'code': 1, 'message': '维度不存在'})
|
||||
|
||||
|
||||
@dimension_bp.route('', methods=['POST'])
|
||||
def create_dimension():
|
||||
"""创建评测维度"""
|
||||
data = request.json
|
||||
insert_data = {
|
||||
'name': data.get('name'),
|
||||
'type': data.get('type'),
|
||||
'description': data.get('description', '')
|
||||
}
|
||||
new_id = generic_create('model_dimension', insert_data)
|
||||
return jsonify({'code': 0, 'message': '创建成功', 'id': new_id})
|
||||
|
||||
|
||||
@dimension_bp.route('/<int:id>', methods=['PUT'])
|
||||
def update_dimension(id):
|
||||
"""更新评测维度"""
|
||||
data = request.json
|
||||
update_data = {}
|
||||
if 'name' in data:
|
||||
update_data['name'] = data['name']
|
||||
if 'type' in data:
|
||||
update_data['type'] = data['type']
|
||||
if 'description' in data:
|
||||
update_data['description'] = data['description']
|
||||
|
||||
if update_data:
|
||||
generic_update('model_dimension', id, update_data)
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@dimension_bp.route('/<int:id>', methods=['DELETE'])
|
||||
def delete_dimension(id):
|
||||
"""删除评测维度"""
|
||||
generic_delete('model_dimension', id)
|
||||
return jsonify({'code': 0, 'message': '删除成功'})
|
||||
@@ -116,7 +116,8 @@ def create_model_manage():
|
||||
'name': data.get('name'),
|
||||
'type': data.get('type'),
|
||||
'model_source': data.get('model_source', 'local'),
|
||||
'description': data.get('description')
|
||||
'description': data.get('description'),
|
||||
'purpose': data.get('purpose', 'inference') # 默认推理用途
|
||||
}
|
||||
|
||||
if data.get('model_source') == 'local':
|
||||
@@ -138,6 +139,17 @@ def update_model_manage(id):
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@model_manage_bp.route('/<int:id>/purpose', methods=['PUT'])
|
||||
def update_model_purpose(id):
|
||||
"""更新模型用途"""
|
||||
data = request.json
|
||||
purpose = data.get('purpose')
|
||||
if purpose not in ['training', 'inference', 'evaluation']:
|
||||
return jsonify({'code': 1, 'message': '无效的用途类型'})
|
||||
generic_update('model_manage', id, {'purpose': purpose})
|
||||
return jsonify({'code': 0, 'message': '更新成功'})
|
||||
|
||||
|
||||
@model_manage_bp.route('/<int:id>', methods=['DELETE'])
|
||||
def delete_model_manage(id):
|
||||
"""删除模型"""
|
||||
|
||||
27
src/main.py
27
src/main.py
@@ -174,6 +174,17 @@ def init_database():
|
||||
api_key VARCHAR(500),
|
||||
model_name VARCHAR(255),
|
||||
description TEXT,
|
||||
purpose VARCHAR(50) DEFAULT 'inference',
|
||||
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 model_dimension (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(100),
|
||||
description TEXT,
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4""",
|
||||
@@ -204,6 +215,13 @@ def init_database():
|
||||
except Exception as e:
|
||||
print(f" 表 {i+1} 创建失败: {e}")
|
||||
|
||||
# 为已存在的 model_manage 表添加 purpose 列
|
||||
try:
|
||||
cursor.execute("ALTER TABLE model_manage ADD COLUMN purpose VARCHAR(50) DEFAULT 'inference'")
|
||||
print(" model_manage 表添加 purpose 列成功")
|
||||
except Exception as e:
|
||||
print(f" model_manage 表 purpose 列处理: {e}")
|
||||
|
||||
# 插入默认管理员用户
|
||||
cursor.execute("SELECT * FROM users WHERE username = 'admin'")
|
||||
if not cursor.fetchone():
|
||||
@@ -221,7 +239,14 @@ def init_database():
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = CONFIG['secret_key']
|
||||
CORS(app, resources={r"/api/*": {"origins": "*"}})
|
||||
app.config['CORS_HEADERS'] = 'Content-Type'
|
||||
CORS(app, resources={
|
||||
r"/api/*": {
|
||||
"origins": "*",
|
||||
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||
"allow_headers": ["Content-Type", "Authorization"]
|
||||
}
|
||||
}, supports_credentials=False)
|
||||
|
||||
# 注册蓝图
|
||||
register_blueprints(app)
|
||||
|
||||
@@ -482,13 +482,23 @@
|
||||
const displayText = textMap[val] || val || '-';
|
||||
return '<span class="px-2 py-1 rounded text-xs bg-blue-100 text-blue-700">' + displayText + '</span>';
|
||||
}},
|
||||
{ title: '用途', key: 'purpose', render: (val) => {
|
||||
const purposeMap = {
|
||||
'training': { text: '训练', class: 'bg-blue-100 text-blue-700' },
|
||||
'inference': { text: '推理', class: 'bg-green-100 text-green-700' },
|
||||
'evaluation': { text: '评测', class: 'bg-purple-100 text-purple-700' }
|
||||
};
|
||||
const display = purposeMap[val] || { text: val || '-', class: 'bg-gray-100 text-gray-700' };
|
||||
return '<span class="px-2 py-1 rounded text-xs ' + display.class + '">' + display.text + '</span>';
|
||||
}},
|
||||
{ title: '模型来源', key: 'model_source', render: (val) => {
|
||||
const textMap = {
|
||||
'local': '本地模型',
|
||||
'api': '在线模型',
|
||||
'online': '在线模型'
|
||||
};
|
||||
const displayText = textMap[val] || val || '-';
|
||||
return '<span class="px-2 py-1 rounded text-xs bg-green-100 text-green-700">' + displayText + '</span>';
|
||||
return '<span class="px-2 py-1 rounded text-xs bg-gray-100 text-gray-700">' + displayText + '</span>';
|
||||
}},
|
||||
{ title: '描述', key: 'description', render: (val) => val || '-' },
|
||||
{ title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' }
|
||||
@@ -778,6 +788,29 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 更新模型用途
|
||||
async function updateModelPurpose(id, purpose) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/model-manage/${id}/purpose`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ purpose })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
// 刷新当前页面
|
||||
const activeLink = document.querySelector('.nav-link.sidebar-item-active');
|
||||
if (activeLink) {
|
||||
loadPage(activeLink.dataset.page);
|
||||
}
|
||||
} else {
|
||||
showMessage('错误', result.message || '更新失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage('错误', '更新失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 切换单个项的选中状态
|
||||
function toggleItemSelection(id, api) {
|
||||
if (selectedItems.has(id)) {
|
||||
@@ -2229,8 +2262,8 @@
|
||||
}
|
||||
|
||||
// ============ 自定义消息弹窗 ============
|
||||
// 显示消息弹窗
|
||||
function showMessage(title, message, type = 'info', onConfirm) {
|
||||
// 显示消息弹窗 - 使用 window 确保全局可访问
|
||||
window.showMessage = function(title, message, type = 'info', onConfirm) {
|
||||
const modal = document.getElementById('customModal');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const modalMessage = document.getElementById('modalMessage');
|
||||
@@ -2263,7 +2296,7 @@
|
||||
modalSingleBtnGroup.classList.remove('hidden');
|
||||
const confirmBtn = modalConfirmBtn2;
|
||||
if (type === 'error') {
|
||||
confirmBtn.className = 'px-6 py-2 bg-danger text-white rounded-lg hover:bg-danger/90 transition-colors';
|
||||
confirmBtn.className = 'px-6 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors';
|
||||
} else {
|
||||
confirmBtn.className = 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
|
||||
}
|
||||
@@ -2272,10 +2305,17 @@
|
||||
modal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// 绑定确认按钮事件
|
||||
confirmBtn.onclick = () => {
|
||||
// 保存回调到按钮属性
|
||||
confirmBtn._onConfirm = onConfirm;
|
||||
|
||||
// 使用 function 而不是箭头函数
|
||||
confirmBtn.onclick = function() {
|
||||
closeModal();
|
||||
if (onConfirm) onConfirm();
|
||||
const callback = this._onConfirm;
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
this._onConfirm = null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2286,8 +2326,8 @@
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
// 确认弹窗(两个按钮)
|
||||
function showConfirm(title, message, onConfirm, onCancel) {
|
||||
// 确认弹窗(两个按钮)- 使用 window 确保全局可访问
|
||||
window.showConfirm = function(title, message, onConfirm, onCancel, type = 'info') {
|
||||
const modal = document.getElementById('customModal');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const modalMessage = document.getElementById('modalMessage');
|
||||
@@ -2296,9 +2336,20 @@
|
||||
const modalCancelBtn = document.getElementById('modalCancelBtn');
|
||||
const modalBtnGroup = document.getElementById('modalBtnGroup');
|
||||
|
||||
if (!modalConfirmBtn) {
|
||||
console.error('modalConfirmBtn not found');
|
||||
return;
|
||||
}
|
||||
|
||||
modalTitle.textContent = title;
|
||||
modalMessage.innerHTML = message;
|
||||
|
||||
// 根据类型设置图标
|
||||
if (type === 'warning') {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-yellow-100 flex items-center justify-center"><i class="fa fa-exclamation text-xl text-yellow-600"></i></div>';
|
||||
} else {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-blue-100 flex items-center justify-center"><i class="fa fa-question text-xl text-blue-600"></i></div>';
|
||||
}
|
||||
|
||||
modalBtnGroup.classList.remove('hidden');
|
||||
modalConfirmBtn.textContent = '确定';
|
||||
@@ -2307,14 +2358,29 @@
|
||||
modal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
modalConfirmBtn.onclick = () => {
|
||||
// 保存回调到按钮属性
|
||||
modalConfirmBtn._onConfirm = onConfirm;
|
||||
modalConfirmBtn._onCancel = onCancel;
|
||||
|
||||
// 使用 function 而不是箭头函数,确保 this 指向正确
|
||||
modalConfirmBtn.onclick = function() {
|
||||
closeModal();
|
||||
if (onConfirm) onConfirm();
|
||||
const callback = this._onConfirm;
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
this._onConfirm = null;
|
||||
this._onCancel = null;
|
||||
};
|
||||
|
||||
modalCancelBtn.onclick = () => {
|
||||
modalCancelBtn.onclick = function() {
|
||||
closeModal();
|
||||
if (onCancel) onCancel();
|
||||
const callback = this._onCancel || modalConfirmBtn._onCancel;
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
modalConfirmBtn._onConfirm = null;
|
||||
modalConfirmBtn._onCancel = null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2382,6 +2448,29 @@
|
||||
window.location.href = 'model-dimension-create.html';
|
||||
}
|
||||
|
||||
// 删除评测维度
|
||||
async function deleteDimension(id) {
|
||||
showConfirm('确认删除', '确定要删除此评测维度吗?', async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/dimension/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
showMessage('成功', '删除成功', 'success', () => {
|
||||
// 刷新维度列表 - 切换到 dimensions tab
|
||||
switchTab(document.querySelector('[data-tab="dimensions"]'), 'dimensions');
|
||||
});
|
||||
} else {
|
||||
showMessage('错误', result.message || '删除失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除维度失败:', error);
|
||||
showMessage('错误', '删除失败: ' + error.message, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 切换 Tab
|
||||
function switchTab(btn, tabId) {
|
||||
// 更新按钮状态
|
||||
|
||||
@@ -337,12 +337,8 @@
|
||||
<label class="form-label">选择大模型 <span class="text-red-500">*</span></label>
|
||||
<select name="eval_model" id="evalModel" class="form-select">
|
||||
<option value="">请选择评估使用的大模型</option>
|
||||
<option value="gpt-4">GPT-4</option>
|
||||
<option value="claude-3">Claude-3</option>
|
||||
<option value="ernie">文心一言</option>
|
||||
<option value="qwen">通义千问</option>
|
||||
<option value="chatglm">ChatGLM</option>
|
||||
</select>
|
||||
<p class="text-xs text-gray-400 mt-1">请在模型管理中配置"评测模型"用途的模型</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">评估方式 <span class="text-red-500">*</span></label>
|
||||
@@ -462,6 +458,35 @@
|
||||
};
|
||||
const API_BASE = getApiBase();
|
||||
|
||||
// 加载评测模型列表(用途为 evaluation 的模型)
|
||||
async function loadEvalModels() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/model-manage`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 0 && result.data) {
|
||||
const evalModels = result.data.filter(m => m.purpose === 'evaluation');
|
||||
const select = document.getElementById('evalModel');
|
||||
|
||||
if (evalModels.length === 0) {
|
||||
select.innerHTML = '<option value="">暂无可用的评测模型</option>';
|
||||
return;
|
||||
}
|
||||
|
||||
select.innerHTML = '<option value="">请选择评估使用的大模型</option>';
|
||||
evalModels.forEach(model => {
|
||||
const option = document.createElement('option');
|
||||
option.value = model.id;
|
||||
option.textContent = `${model.name} (${model.model_source === 'api' ? 'API' : '本地'})`;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载评测模型列表失败:', error);
|
||||
document.getElementById('evalModel').innerHTML = '<option value="">加载失败</option>';
|
||||
}
|
||||
}
|
||||
|
||||
// 返回列表页
|
||||
function goBack() {
|
||||
window.location.href = 'main.html?page=model-eval';
|
||||
@@ -935,13 +960,16 @@
|
||||
};
|
||||
|
||||
// 初始化函数
|
||||
function initPage() {
|
||||
async function initPage() {
|
||||
// 绑定指标类型下拉框事件
|
||||
const dimensionType = document.getElementById('dimensionType');
|
||||
if (dimensionType) {
|
||||
dimensionType.addEventListener('change', toggleEvalConfig);
|
||||
}
|
||||
|
||||
// 加载评测模型列表
|
||||
await loadEvalModels();
|
||||
|
||||
// 绑定 Markdown 编辑器事件
|
||||
const evalPromptEditor = document.getElementById('evalPromptEditor');
|
||||
if (evalPromptEditor) {
|
||||
@@ -1076,13 +1104,14 @@
|
||||
const name = formData.get('name').trim();
|
||||
const type = formData.get('type').trim();
|
||||
|
||||
// 验证
|
||||
if (!name) {
|
||||
alert('请输入维度名称');
|
||||
showMessage('提示', '请输入维度名称', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
alert('请选择指标类型');
|
||||
showMessage('提示', '请选择指标类型', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1093,22 +1122,22 @@
|
||||
const evalMethod = formData.get('eval_method');
|
||||
|
||||
if (!evalModel) {
|
||||
alert('请选择评估使用的大模型');
|
||||
showMessage('提示', '请选择评估使用的大模型', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!evalPrompt) {
|
||||
alert('请输入评估 Prompt');
|
||||
showMessage('提示', '请输入评估 Prompt', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!evalMethod) {
|
||||
alert('请选择评估方式');
|
||||
showMessage('提示', '请选择评估方式', 'warning');
|
||||
return;
|
||||
}
|
||||
} else if (type === 'text_similarity') {
|
||||
// 规则评估:获取所有选中的评估方式
|
||||
const checkedMethods = Array.from(document.querySelectorAll('input[name="eval_method"]:checked')).map(cb => cb.value);
|
||||
if (checkedMethods.length === 0) {
|
||||
alert('请至少选择一个评估方式');
|
||||
showMessage('提示', '请至少选择一个评估方式', 'warning');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1147,22 +1176,92 @@
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/model-eval/dimension`, {
|
||||
const response = await fetch(`${API_BASE}/dimension`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
alert('评测维度创建成功!');
|
||||
showMessage('成功', '评测维度创建成功!', 'success', () => {
|
||||
goBack();
|
||||
});
|
||||
} else {
|
||||
alert('创建失败: ' + (result.message || '未知错误'));
|
||||
showMessage('错误', result.message || '创建失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('创建失败: ' + error.message);
|
||||
showMessage('错误', '创建失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 自定义消息弹窗 ============
|
||||
function showMessage(title, message, type = 'info', onConfirm) {
|
||||
const modal = document.getElementById('customModal');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const modalMessage = document.getElementById('modalMessage');
|
||||
const modalIcon = document.getElementById('modalIcon');
|
||||
const modalConfirmBtn = document.getElementById('modalConfirmBtn2');
|
||||
const modalBtnGroup = document.getElementById('modalBtnGroup');
|
||||
const modalSingleBtnGroup = document.getElementById('modalSingleBtnGroup');
|
||||
|
||||
if (!modalConfirmBtn) {
|
||||
console.error('modalConfirmBtn2 not found');
|
||||
return;
|
||||
}
|
||||
|
||||
modalTitle.textContent = title;
|
||||
modalMessage.innerHTML = message;
|
||||
|
||||
if (type === 'success') {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-green-100 flex items-center justify-center"><i class="fa fa-check text-xl text-green-600"></i></div>';
|
||||
} else if (type === 'error') {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-red-100 flex items-center justify-center"><i class="fa fa-times text-xl text-red-600"></i></div>';
|
||||
} else if (type === 'warning') {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-yellow-100 flex items-center justify-center"><i class="fa fa-exclamation text-xl text-yellow-600"></i></div>';
|
||||
} else {
|
||||
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-blue-100 flex items-center justify-center"><i class="fa fa-info text-xl text-blue-600"></i></div>';
|
||||
}
|
||||
|
||||
// 隐藏双按钮组,显示单按钮组
|
||||
if (modalBtnGroup) modalBtnGroup.classList.add('hidden');
|
||||
modalSingleBtnGroup.classList.remove('hidden');
|
||||
modalConfirmBtn.className = type === 'error' ? 'px-6 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors' : 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// 保存回调到按钮属性
|
||||
modalConfirmBtn._onConfirm = onConfirm;
|
||||
|
||||
// 使用 function 确保 this 指向正确
|
||||
modalConfirmBtn.onclick = function() {
|
||||
modal.classList.add('hidden');
|
||||
document.body.style.overflow = '';
|
||||
const callback = this._onConfirm;
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
this._onConfirm = null;
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 自定义消息弹窗 -->
|
||||
<div id="customModal" class="hidden fixed inset-0 bg-black/50 z-50 flex items-center justify-center">
|
||||
<div class="bg-white rounded-xl shadow-xl max-w-md w-full mx-4 overflow-hidden">
|
||||
<div class="p-6 text-center">
|
||||
<div id="modalIcon"></div>
|
||||
<h3 id="modalTitle" class="text-lg font-medium text-gray-800 mb-2"></h3>
|
||||
<p id="modalMessage" class="text-gray-600 text-sm"></p>
|
||||
</div>
|
||||
<div id="modalBtnGroup" class="hidden px-6 pb-6 flex justify-center space-x-4">
|
||||
<button id="modalCancelBtn" class="px-6 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors">取消</button>
|
||||
<button id="modalConfirmBtn" class="px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors">确定</button>
|
||||
</div>
|
||||
<div id="modalSingleBtnGroup" class="px-6 pb-6 flex justify-center">
|
||||
<button id="modalConfirmBtn2" class="px-6 py-2 text-white rounded-lg transition-colors">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -114,6 +114,10 @@
|
||||
activeContent.classList.remove('hidden');
|
||||
activeContent.classList.add('active');
|
||||
}
|
||||
// 切换到评测维度tab时刷新数据
|
||||
if (tabId === 'dimensions') {
|
||||
loadDimensions();
|
||||
}
|
||||
};
|
||||
|
||||
// 加载评测任务数据
|
||||
@@ -195,26 +199,34 @@
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 加载评测维度数据(模拟数据)
|
||||
// 加载评测维度数据
|
||||
async function loadDimensions() {
|
||||
// 模拟评测维度数据
|
||||
const mockData = [
|
||||
{ id: 1, name: '准确率', type: 'accuracy', desc: '模型预测正确的比例', status: '启用' },
|
||||
{ id: 2, name: '召回率', type: 'recall', desc: '找出所有正例的能力', status: '启用' },
|
||||
{ id: 3, name: 'F1分数', type: 'f1', desc: '准确率和召回率的调和平均', status: '启用' },
|
||||
{ id: 4, name: '推理速度', type: 'speed', desc: '模型推理响应时间', status: '启用' },
|
||||
{ id: 5, name: 'BLEU分数', type: 'bleu', desc: '机器翻译质量评估', status: '停用' }
|
||||
];
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/dimension`);
|
||||
const result = await response.json();
|
||||
|
||||
const tbody = document.getElementById('dimensionsBody');
|
||||
tbody.innerHTML = mockData.map(item => `
|
||||
|
||||
if (result.code !== 0 || !result.data || result.data.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="px-6 py-12 text-center text-gray-400">
|
||||
<i class="fa fa-inbox text-4xl mb-3"></i>
|
||||
<p>暂无评测维度</p>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = result.data.map(item => `
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${item.name}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.type}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.desc}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${item.name || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.type || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.description || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span class="px-2 py-1 text-xs font-medium rounded-full ${item.status === '启用' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-500'}">
|
||||
${item.status}
|
||||
<span class="px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-700">
|
||||
启用
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
@@ -227,6 +239,9 @@
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
} catch (error) {
|
||||
console.error('加载评测维度失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 状态样式
|
||||
@@ -267,9 +282,36 @@
|
||||
alert('编辑维度功能开发中');
|
||||
}
|
||||
|
||||
function deleteDimension(id) {
|
||||
if (confirm('确定要删除此维度吗?')) {
|
||||
alert('删除维度功能开发中');
|
||||
async function deleteDimension(id) {
|
||||
showConfirm('确认删除', '确定要删除此评测维度吗?', () => {
|
||||
executeDelete(id);
|
||||
});
|
||||
}
|
||||
|
||||
async function executeDelete(id) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/dimension/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
// 切换到评测维度tab并刷新列表
|
||||
switchToDimensionsTab();
|
||||
showMessage('成功', '删除成功', 'success');
|
||||
} else {
|
||||
showMessage('错误', result.message || '删除失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除维度失败:', error);
|
||||
showMessage('错误', '删除失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 切换到评测维度tab
|
||||
function switchToDimensionsTab() {
|
||||
const dimBtn = document.querySelector('[data-tab="dimensions"]');
|
||||
if (dimBtn) {
|
||||
dimBtn.click();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
<!-- 基本信息 -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">基本信息</h3>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="max-w-xl">
|
||||
<div>
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>模型名称
|
||||
@@ -212,7 +212,7 @@
|
||||
<input type="text" name="name" class="form-input" placeholder="请输入模型名称" maxlength="100">
|
||||
<p class="text-xs text-gray-400 mt-1">支持中文、英文、数字、下划线,最多100个字符</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mt-4">
|
||||
<label class="form-label">
|
||||
<span class="text-red-500 mr-1">*</span>模型类型
|
||||
</label>
|
||||
@@ -225,6 +225,26 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 模型用途 -->
|
||||
<div class="mt-8 mb-6">
|
||||
<label class="form-label flex items-center mb-3">
|
||||
<span class="text-red-500 mr-1">*</span>模型用途
|
||||
</label>
|
||||
<div class="flex items-center space-x-6">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="purpose" value="training" class="mr-2">
|
||||
<span class="text-sm">训练基座模型</span>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="purpose" value="inference" class="mr-2" checked>
|
||||
<span class="text-sm">推理对比模型</span>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="radio" name="purpose" value="evaluation" class="mr-2">
|
||||
<span class="text-sm">评测模型</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
@@ -430,6 +450,12 @@
|
||||
const descInput = document.querySelector('textarea[name="description"]');
|
||||
descInput.value = model.description || '';
|
||||
document.getElementById('descCount').textContent = descInput.value.length;
|
||||
|
||||
// 填充用途(兼容旧数据没有 purpose 字段的情况)
|
||||
const purpose = model.purpose || 'inference';
|
||||
document.querySelectorAll('input[name="purpose"]').forEach(radio => {
|
||||
radio.checked = radio.value === purpose;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载模型数据失败:', error);
|
||||
@@ -458,13 +484,15 @@
|
||||
const form = document.getElementById('modelForm');
|
||||
const formData = new FormData(form);
|
||||
const modelSource = formData.get('model_source');
|
||||
console.log('modelSource:', modelSource);
|
||||
const purpose = formData.get('purpose');
|
||||
console.log('modelSource:', modelSource, 'purpose:', purpose);
|
||||
|
||||
const data = {
|
||||
name: formData.get('name'),
|
||||
type: formData.get('type'),
|
||||
model_source: modelSource,
|
||||
description: formData.get('description')
|
||||
description: formData.get('description'),
|
||||
purpose: purpose
|
||||
};
|
||||
|
||||
// 根据模型来源设置不同的字段
|
||||
|
||||
286
web/pages/model-manage.html
Normal file
286
web/pages/model-manage.html
Normal file
@@ -0,0 +1,286 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>模型管理 / 远光软件微调平台</title>
|
||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.bg-primary { background-color: #1890ff; }
|
||||
.text-primary { color: #1890ff; }
|
||||
.border-primary { border-color: #1890ff; }
|
||||
:root { --primary: #1890ff; }
|
||||
.form-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
color: #1f2937;
|
||||
background-color: #fff;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.375rem;
|
||||
transition: border-color 0.15s ease-in-out;
|
||||
}
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.1);
|
||||
}
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.tab-btn {
|
||||
padding: 8px 20px !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 500 !important;
|
||||
border-radius: 6px !important;
|
||||
border: 1px solid transparent !important;
|
||||
cursor: pointer !important;
|
||||
background: transparent !important;
|
||||
color: #4b5563 !important;
|
||||
transition: all 0.2s !important;
|
||||
min-width: 100px;
|
||||
}
|
||||
.tab-btn:hover {
|
||||
background: white !important;
|
||||
border-color: #d1d5db !important;
|
||||
}
|
||||
.tab-btn.tab-active {
|
||||
background: white !important;
|
||||
color: #1890ff !important;
|
||||
border-color: #1890ff !important;
|
||||
box-shadow: 0 1px 3px rgba(24,144,255,0.2) !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="antialiased bg-gray-50">
|
||||
<!-- Tab 导航 -->
|
||||
<div style="background: #fff; border-bottom: 1px solid #e5e7eb; padding: 16px;">
|
||||
<h2 style="font-size: 18px; font-weight: 600; color: #1f2937; margin-bottom: 12px;">模型管理</h2>
|
||||
<div style="display: flex; gap: 8px; background: #f3f4f6; padding: 6px; border-radius: 8px;">
|
||||
<button onclick="switchTab('all')" id="tab-all" class="tab-btn tab-active" style="display: inline-flex; align-items: center; justify-content: center;">
|
||||
全部模型
|
||||
</button>
|
||||
<button onclick="switchTab('training')" id="tab-training" class="tab-btn" style="display: inline-flex; align-items: center; justify-content: center;">
|
||||
训练基座
|
||||
</button>
|
||||
<button onclick="switchTab('inference')" id="tab-inference" class="tab-btn" style="display: inline-flex; align-items: center; justify-content: center;">
|
||||
推理对比
|
||||
</button>
|
||||
<button onclick="switchTab('evaluation')" id="tab-evaluation" class="tab-btn" style="display: inline-flex; align-items: center; justify-content: center;">
|
||||
评测模型
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格容器 -->
|
||||
<div style="padding: 16px;">
|
||||
<!-- 工具栏 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
||||
<div style="display: flex; align-items: center; gap: 16px;">
|
||||
<input type="text" id="searchInput" placeholder="搜索模型名称..." style="width: 256px; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px;" oninput="filterModels()">
|
||||
</div>
|
||||
<button onclick="window.location.href='model-manage-create.html'" style="padding: 8px 16px; background: #1890ff; color: white; border: none; border-radius: 6px; cursor: pointer; display: flex; align-items: center; font-size: 14px;">
|
||||
<span style="margin-right: 8px;">+</span>添加模型
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 模型表格 -->
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">模型名称</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">模型类型</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">用途</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">模型来源</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">描述</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">创建时间</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="modelsBody" class="bg-white divide-y divide-gray-200">
|
||||
<!-- 动态加载 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- 空状态 -->
|
||||
<div id="emptyState" class="hidden px-6 py-12 text-center">
|
||||
<i class="fa fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="text-gray-500">暂无模型数据</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 动态获取 API 基础地址
|
||||
const getApiBase = () => {
|
||||
const protocol = window.location.protocol;
|
||||
const hostname = window.location.hostname;
|
||||
return `${protocol}//${hostname}:8080/api`;
|
||||
};
|
||||
const API_BASE = getApiBase();
|
||||
|
||||
let allModels = [];
|
||||
let currentTab = 'all';
|
||||
|
||||
// Tab 切换
|
||||
function switchTab(tab) {
|
||||
currentTab = tab;
|
||||
// 更新按钮样式
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.classList.remove('tab-active');
|
||||
});
|
||||
const activeTab = document.getElementById(`tab-${tab}`);
|
||||
activeTab.classList.add('tab-active');
|
||||
|
||||
renderModels();
|
||||
}
|
||||
|
||||
// 加载模型数据
|
||||
async function loadModels() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/model-manage`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 0) {
|
||||
allModels = result.data || [];
|
||||
renderModels();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载模型失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 筛选模型
|
||||
function filterModels() {
|
||||
renderModels();
|
||||
}
|
||||
|
||||
// 渲染模型列表
|
||||
function renderModels() {
|
||||
const searchValue = document.getElementById('searchInput').value.toLowerCase();
|
||||
let filteredModels = allModels;
|
||||
|
||||
// 按 Tab 筛选
|
||||
if (currentTab !== 'all') {
|
||||
filteredModels = filteredModels.filter(m => m.purpose === currentTab);
|
||||
}
|
||||
|
||||
// 按搜索关键词筛选
|
||||
if (searchValue) {
|
||||
filteredModels = filteredModels.filter(m =>
|
||||
m.name?.toLowerCase().includes(searchValue) ||
|
||||
m.description?.toLowerCase().includes(searchValue)
|
||||
);
|
||||
}
|
||||
|
||||
const tbody = document.getElementById('modelsBody');
|
||||
const emptyState = document.getElementById('emptyState');
|
||||
|
||||
if (filteredModels.length === 0) {
|
||||
tbody.innerHTML = '';
|
||||
emptyState.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
emptyState.classList.add('hidden');
|
||||
|
||||
tbody.innerHTML = filteredModels.map(item => {
|
||||
// 模型类型
|
||||
const typeMap = {
|
||||
'LLM': '大语言模型',
|
||||
'CV': '计算机视觉',
|
||||
'NLP': '自然语言处理',
|
||||
'Embedding': '向量模型',
|
||||
'Other': '其他'
|
||||
};
|
||||
const typeDisplay = typeMap[item.type] || item.type || '-';
|
||||
|
||||
// 用途
|
||||
const purposeMap = {
|
||||
'training': { text: '训练', class: 'bg-blue-100 text-blue-700' },
|
||||
'inference': { text: '推理', class: 'bg-green-100 text-green-700' },
|
||||
'evaluation': { text: '评测', class: 'bg-purple-100 text-purple-700' }
|
||||
};
|
||||
const purposeDisplay = purposeMap[item.purpose] || purposeMap['inference'];
|
||||
|
||||
// 模型来源
|
||||
const sourceMap = {
|
||||
'local': '本地模型',
|
||||
'api': '在线模型',
|
||||
'online': '在线模型'
|
||||
};
|
||||
const sourceDisplay = sourceMap[item.model_source] || item.model_source || '-';
|
||||
|
||||
return `
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="text-sm font-medium text-gray-900">${item.name || '-'}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span class="px-2 py-1 text-xs font-medium rounded bg-blue-100 text-blue-700">${typeDisplay}</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span class="px-2 py-1 text-xs font-medium rounded ${purposeDisplay.class}">${purposeDisplay.text}</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span class="px-2 py-1 text-xs font-medium rounded bg-gray-100 text-gray-700">${sourceDisplay}</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="text-sm text-gray-500 max-w-xs truncate">${item.description || '-'}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
${item.create_time ? new Date(item.create_time).toLocaleString('zh-CN') : '-'}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<button onclick="editModel(${item.id})" class="text-primary hover:text-primary/80 mr-3">
|
||||
<i class="fa fa-edit"></i> 编辑
|
||||
</button>
|
||||
<button onclick="deleteModel(${item.id})" class="text-red-500 hover:text-red-600">
|
||||
<i class="fa fa-trash"></i> 删除
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 编辑模型
|
||||
function editModel(id) {
|
||||
window.location.href = `model-manage-create.html?id=${id}`;
|
||||
}
|
||||
|
||||
// 删除模型
|
||||
async function deleteModel(id) {
|
||||
if (!confirm('确定要删除此模型吗?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/model-manage/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 0) {
|
||||
loadModels();
|
||||
} else {
|
||||
alert('删除失败: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除模型失败:', error);
|
||||
alert('删除失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时初始化
|
||||
loadModels();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user