Files
YG_FT_Platform/web/pages/model-eval.html
WIN-JHFT4D3SIVT\caoxiaozhu 40ca89fad5 1. 修改了应用端口的问题
2. 增加了创建虚拟环境的脚本和删除虚拟环境的脚本
2026-01-26 14:33:00 +08:00

446 lines
22 KiB
HTML

<!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; }
</style>
</head>
<body class="antialiased bg-gray-50">
<!-- 表格容器 -->
<div id="tableContainer">
<!-- 评测任务表格 -->
<div id="tasksTable" class="tab-content active">
<div class="bg-white rounded-lg shadow-sm">
<div class="px-6 py-4 border-b border-gray-100">
<h3 class="text-lg font-medium text-gray-800">评测任务列表</h3>
</div>
<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="tasksBody" class="bg-white divide-y divide-gray-200">
<!-- 动态加载 -->
</tbody>
</table>
</div>
</div>
</div>
<!-- 排行榜表格 -->
<div id="leaderboardTable" class="tab-content hidden">
<div class="bg-white rounded-lg shadow-sm">
<div class="px-6 py-4 border-b border-gray-100">
<h3 class="text-lg font-medium text-gray-800">模型排行榜</h3>
</div>
<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>
</tr>
</thead>
<tbody id="leaderboardBody" class="bg-white divide-y divide-gray-200">
<!-- 动态加载 -->
</tbody>
</table>
</div>
</div>
</div>
<!-- 评测维度表格 -->
<div id="dimensionsTable" class="tab-content hidden">
<div class="bg-white rounded-lg shadow-sm">
<div class="px-6 py-4 border-b border-gray-100">
<h3 class="text-lg font-medium text-gray-800">评测维度管理</h3>
</div>
<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>
</tr>
</thead>
<tbody id="dimensionsBody" class="bg-white divide-y divide-gray-200">
<!-- 动态加载 -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 自定义确认弹窗 -->
<div id="confirmModal" 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-sm w-full mx-4 overflow-hidden">
<div class="p-6 text-center">
<div id="confirmIcon" 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>
<h3 id="confirmTitle" class="text-lg font-medium text-gray-800 mb-2"></h3>
<p id="confirmMessage" class="text-gray-600 text-sm"></p>
</div>
<div class="px-6 pb-6 flex justify-center space-x-4">
<button id="confirmCancelBtn" class="px-6 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors">取消</button>
<button id="confirmOkBtn" class="px-6 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors">确定</button>
</div>
</div>
</div>
<script>
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
const getApiBase = () => {
const protocol = window.location.protocol;
const hostname = window.location.hostname;
return `${protocol}//${hostname}:7861/api`;
};
const API_BASE = getApiBase();
// Tab 内容切换函数(供 main.html 调用)
window.switchTabContent = function(tabId) {
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.add('hidden');
content.classList.remove('active');
});
const activeContent = document.getElementById(tabId + 'Table');
if (activeContent) {
activeContent.classList.remove('hidden');
activeContent.classList.add('active');
}
// 切换到评测维度tab时刷新数据
if (tabId === 'dimensions') {
loadDimensions();
}
};
// 加载评测任务数据
async function loadTasks() {
try {
const response = await fetch(`${API_BASE}/model-eval`);
const result = await response.json();
if (result.code === 0) {
const data = result.data || [];
const tbody = document.getElementById('tasksBody');
if (data.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="7" 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 = 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.model_name || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.dataset || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.metric || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.score ? item.score + '%' : '-'}</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-medium rounded-full ${getStatusClass(item.status)}">
${item.status || '未知'}
</span>
</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="viewReport(${item.id})" class="text-primary hover:text-primary/80 mr-3">
<i class="fa fa-bar-chart"></i> 报告
</button>
<button onclick="deleteTask(${item.id})" class="text-red-500 hover:text-red-600">
<i class="fa fa-trash"></i> 删除
</button>
</td>
</tr>
`).join('');
}
} catch (error) {
console.error('加载评测任务失败:', error);
}
}
// 加载排行榜数据(模拟数据)
async function loadLeaderboard() {
// 模拟排行榜数据
const mockData = [
{ rank: 1, model_name: 'GPT-4', avg_score: 95.2, eval_count: 128, last_eval: '2026-01-20' },
{ rank: 2, model_name: 'Claude-3', avg_score: 93.8, eval_count: 96, last_eval: '2026-01-19' },
{ rank: 3, model_name: '文心一言', avg_score: 89.5, eval_count: 64, last_eval: '2026-01-18' },
{ rank: 4, model_name: '通义千问', avg_score: 87.3, eval_count: 52, last_eval: '2026-01-17' },
{ rank: 5, model_name: 'ChatGLM', avg_score: 84.6, eval_count: 45, last_eval: '2026-01-16' }
];
const tbody = document.getElementById('leaderboardBody');
tbody.innerHTML = mockData.map(item => `
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-bold rounded-full ${getRankClass(item.rank)}">
#${item.rank}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${item.model_name}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.avg_score}%</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.eval_count}次</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.last_eval}</td>
</tr>
`).join('');
}
// 加载评测维度数据
window.loadDimensions = async function() {
try {
const response = await fetch(`${API_BASE}/dimension`);
const result = await response.json();
const tbody = document.getElementById('dimensionsBody');
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.description || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap">
<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">
<button onclick="editDimension(${item.id})" class="text-primary hover:text-primary/80 mr-3">
<i class="fa fa-edit"></i> 编辑
</button>
<button onclick="deleteDimension(${item.id})" class="text-red-500 hover:text-red-600">
<i class="fa fa-trash"></i> 删除
</button>
</td>
</tr>
`).join('');
} catch (error) {
console.error('加载评测维度失败:', error);
}
}
// 状态样式
function getStatusClass(status) {
const map = {
'completed': 'bg-green-100 text-green-700',
'running': 'bg-blue-100 text-blue-700',
'pending': 'bg-yellow-100 text-yellow-700',
'failed': 'bg-red-100 text-red-700'
};
return map[status?.toLowerCase()] || 'bg-gray-100 text-gray-500';
}
// 排名样式
function getRankClass(rank) {
if (rank === 1) return 'bg-yellow-100 text-yellow-700';
if (rank === 2) return 'bg-gray-100 text-gray-600';
if (rank === 3) return 'bg-orange-100 text-orange-700';
return 'bg-gray-50 text-gray-500';
}
// 自定义确认弹窗
function showConfirm(title, message, onConfirm) {
const modal = document.getElementById('confirmModal');
const modalTitle = document.getElementById('confirmTitle');
const modalMessage = document.getElementById('confirmMessage');
const cancelBtn = document.getElementById('confirmCancelBtn');
const okBtn = document.getElementById('confirmOkBtn');
modalTitle.textContent = title;
modalMessage.textContent = message;
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
cancelBtn.onclick = function() {
modal.classList.add('hidden');
document.body.style.overflow = '';
};
okBtn.onclick = function() {
modal.classList.add('hidden');
document.body.style.overflow = '';
if (typeof onConfirm === 'function') {
onConfirm();
}
};
}
// 自定义消息弹窗
function showMessage(title, message, type = 'info', onConfirm) {
const modal = document.getElementById('confirmModal');
const modalTitle = document.getElementById('confirmTitle');
const modalMessage = document.getElementById('confirmMessage');
const cancelBtn = document.getElementById('confirmCancelBtn');
const okBtn = document.getElementById('confirmOkBtn');
// 隐藏取消按钮,只显示确定按钮
cancelBtn.classList.add('hidden');
modalTitle.textContent = title;
modalMessage.innerHTML = message;
// 根据类型设置图标和按钮颜色
const iconContainer = document.getElementById('confirmIcon');
if (type === 'success') {
iconContainer.className = 'w-12 h-12 mx-auto mb-4 rounded-full bg-green-100 flex items-center justify-center';
iconContainer.innerHTML = '<i class="fa fa-check text-xl text-green-600"></i>';
okBtn.className = 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
} else if (type === 'error') {
iconContainer.className = 'w-12 h-12 mx-auto mb-4 rounded-full bg-red-100 flex items-center justify-center';
iconContainer.innerHTML = '<i class="fa fa-times text-xl text-red-600"></i>';
okBtn.className = 'px-6 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors';
} else if (type === 'warning') {
iconContainer.className = 'w-12 h-12 mx-auto mb-4 rounded-full bg-yellow-100 flex items-center justify-center';
iconContainer.innerHTML = '<i class="fa fa-exclamation text-xl text-yellow-600"></i>';
okBtn.className = 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
} else {
iconContainer.className = 'w-12 h-12 mx-auto mb-4 rounded-full bg-blue-100 flex items-center justify-center';
iconContainer.innerHTML = '<i class="fa fa-info text-xl text-blue-600"></i>';
okBtn.className = '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';
okBtn.onclick = function() {
modal.classList.add('hidden');
document.body.style.overflow = '';
// 恢复取消按钮
cancelBtn.classList.remove('hidden');
if (typeof onConfirm === 'function') {
onConfirm();
}
};
}
// 操作函数(挂载到 window 以便 onclick 调用)
window.viewReport = function(id) {
alert('查看报告功能开发中');
};
window.deleteTask = function(id) {
showConfirm('确认删除', '确定要删除此评测任务吗?', () => {
executeDeleteTask(id);
});
};
async function executeDeleteTask(id) {
try {
const response = await fetch(`${API_BASE}/model-eval/${id}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.code === 0) {
showMessage('成功', '删除成功', 'success', () => {
loadTasks();
});
} else {
showMessage('错误', result.message || '删除失败', 'error');
}
} catch (error) {
console.error('删除评测任务失败:', error);
showMessage('错误', '删除失败: ' + error.message, 'error');
}
}
window.addDimension = function() {
window.location.href = 'model-dimension-create.html';
};
window.editDimension = function(id) {
window.location.href = `model-dimension-create.html?id=${id}`;
};
window.deleteDimension = function(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
window.switchToDimensionsTab = function() {
const dimBtn = document.querySelector('[data-tab="dimensions"]');
if (dimBtn) {
dimBtn.click();
}
};
// 页面加载时初始化
async function initPage() {
await loadTasks();
loadLeaderboard();
loadDimensions();
}
// 支持通过 main.html fetch 加载
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPage);
} else {
initPage();
}
</script>
</body>
</html>