Files
YG_FT_Platform/web/js/main.js
WIN-JHFT4D3SIVT\caoxiaozhu 513e96082c 重构了main.html的主函数
重构了大量的页面的sidebar
优化了代码结构
2026-02-02 09:22:52 +08:00

598 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 主入口模块
* 页面初始化和导航控制
*/
// 各功能模块的表格配置
window.tableConfigs = {
'fine-tune': {
title: '模型调优',
api: 'fine-tune',
hasCreate: true,
createText: '创建训练任务',
columns: [
{ title: '任务名称', key: 'name' },
{ title: '任务状态', key: 'status', render: (val) => `<span class="px-2 py-1 rounded text-xs ${val === 'running' ? 'bg-green-100 text-green-700' : val === 'failed' ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-700'}">${val}</span>` },
{ title: '训练方式', key: 'train_type', render: (val) => val === 'SFT' ? 'SFT 微调训练' : (val === 'DPO' ? 'DPO 偏好训练' : (val === 'CPT' ? 'CPT 继续预训练' : '-')) },
{ title: '训练模板', key: 'template', render: (val) => val || '-' },
{ title: '基座模型', key: 'base_model', render: (val, row) => `<span class="model-name-cell" data-model-id="${val}">加载中...</span>` }
],
actions: ['stop', 'logs', 'delete']
},
'my-models': {
title: '我的模型',
api: 'model-manage/trained-models',
dataPath: 'models',
hasCreate: false,
columns: [
{ title: '模型名称', key: 'name' },
{ title: '训练方法', key: 'train_methods', render: (val) => val && val[0] ? val[0].name : '-' },
{ title: '基座模型', key: 'base_model_path', render: (val) => `<span class="text-xs text-gray-500 truncate block" title="${val}">${val || '-'}</span>` },
{ title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' }
],
actions: ['view', 'delete']
},
'model-eval': {
title: '模型评测',
isExternalPage: true,
createConfig: {
page: 'model-eval-create',
hasCreate: true,
createText: '新建评测'
}
},
'model-compare': {
title: '模型对比',
api: 'model-compare',
hasCreate: true,
createText: '新建对比',
columns: [
{ title: '对比名称', key: 'model_name' },
{ title: '描述', key: 'description', render: (val) => val || '-' },
{ title: '相关模型', key: 'models', render: (val) => {
if (!val) return '-';
try {
// 如果是字符串,尝试解析 JSON
let models = val;
if (typeof val === 'string') {
try {
models = JSON.parse(val);
} catch {
models = val.split(',').map(id => ({ model_name: id.trim() }));
}
}
// 如果是数组,提取模型名称
if (Array.isArray(models) && models.length > 0) {
return models.map(function(m) {
if (typeof m === 'object' && m !== null) {
return m.model_name || m.name || '未知模型';
}
return String(m);
}).join(', ');
}
// 如果是单个对象
if (typeof models === 'object' && models !== null) {
return models.model_name || models.name || '未知模型';
}
return String(models);
} catch (e) {
return '解析错误';
}
}},
{ title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' }
],
actions: ['compare', 'delete']
},
'dataset-manage': {
title: '数据集管理',
api: 'dataset-manage',
hasCreate: true,
createText: '上传数据集',
columns: [
{ title: '数据集名称', key: 'name' },
{ title: '数据类型', key: 'type', render: (val) => {
const textMap = {
'train': '训练数据',
'test': '测试数据',
'eval': '评测数据',
'val': '验证数据',
'other': '其他'
};
const displayText = textMap[val?.toLowerCase()] || val || '-';
return '<span class="px-2 py-1 rounded text-xs bg-blue-100 text-blue-700">' + displayText + '</span>';
}},
{ title: '存储位置', key: 'storage_type', render: (val) => {
const textMap = {
'local': '本地存储',
'minio': 'MinIO',
'cloud': '云存储'
};
const displayText = textMap[val] || val || '-';
return '<span class="px-2 py-1 rounded text-xs bg-green-100 text-green-700">' + displayText + '</span>';
}},
{ title: '大小', key: 'size', render: (val) => (val && val !== '0 B' && val !== '0') ? val : '-' },
{ title: '数据条数', key: 'count', render: (val) => val || 0 },
{ title: '描述', key: 'description', render: (val) => val || '-' },
{ title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' }
],
actions: ['preview', 'download', 'delete']
},
'data-generate': {
title: '其他工具',
isTools: true,
defaultTools: [
{ id: 'data-generate', name: '数据生成', icon: 'fa-database', description: '基于LLM生成微调数据集' },
{ id: 'json2jsonl', name: 'JSON转JSONL', icon: 'fa-code', description: '将JSON文件转换为JSONL格式' },
{ id: 'md-convert', name: '转换Markdown', icon: 'fa-file-text', description: '将Markdown文件转换为训练数据' }
],
customTools: []
},
'model-manage': {
title: '模型管理',
api: 'model-manage',
hasCreate: true,
hasModelTabs: true,
createText: '添加模型',
columns: [
{ title: '模型名称', key: 'name' },
{ title: '模型类型', key: 'type', render: (val) => {
const textMap = {
'LLM': '大语言模型',
'CV': '计算机视觉',
'NLP': '自然语言处理',
'Embedding': '向量模型',
'Other': '其他'
};
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-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') : '-' }
],
actions: ['edit', 'delete']
},
'config': {
title: '平台性能',
skipFetch: true,
hasCreate: false,
isHardwareMonitor: true
},
'logs': {
title: '查看日志',
skipFetch: true,
hasCreate: false,
isLogViewer: true
},
'model-compare-chat': {
title: '模型对比',
skipFetch: true,
hasCreate: false,
isExternalPage: true
},
'model-compare-result': {
title: '对比结果',
skipFetch: true,
hasCreate: false,
isExternalPage: true
},
'training-log': {
title: '训练日志',
skipFetch: true,
hasCreate: false,
isExternalPage: true
}
};
// 操作按钮映射
window.actionLabels = {
'stop': '停止',
'logs': '查看日志',
'delete': '删除',
'deploy': '部署',
'eval': '评测',
'report': '查看报告',
'scale': '扩容',
'preview': '预览',
'download': '下载',
'detail': '详情',
'edit': '编辑',
'compare': '开始对话',
'chat': '对话',
'view': '合并权重'
};
// 加载模型列表缓存
async function loadModelListCache() {
try {
const response = await fetch(`${window.API_BASE}/model-manage`);
const result = await response.json();
if (result.code === 0) {
window.modelListCache = result.data || [];
}
} catch (e) {
console.error('加载模型列表失败:', e);
window.modelListCache = [];
}
}
// 根据模型ID获取模型名称同步版本
function getModelName(modelId) {
if (!modelId) return '-';
const model = window.modelListCache.find(m =>
m.id == modelId ||
m.id === String(modelId) ||
m.id === Number(modelId)
);
if (model) {
return model.name;
}
return `模型${modelId}`;
}
// 异步获取模型名称并更新 DOM
async function fetchAndUpdateModelName(modelId, cellElement) {
if (!modelId) {
cellElement.textContent = '-';
return;
}
let model = window.modelListCache.find(m =>
m.id == modelId ||
m.id === String(modelId) ||
m.id === Number(modelId)
);
if (!model) {
try {
const response = await fetch(`${window.API_BASE}/model-manage`);
const result = await response.json();
if (result.code === 0) {
window.modelListCache = result.data || [];
model = window.modelListCache.find(m =>
m.id == modelId ||
m.id === String(modelId) ||
m.id === Number(modelId)
);
}
} catch (e) {
console.error('获取模型列表失败:', e);
}
}
if (model) {
cellElement.textContent = model.name;
} else {
cellElement.textContent = `模型${modelId}`;
}
}
// 根据模型ID列表获取模型名称列表
function getModelNames(modelIds) {
if (!modelIds || !Array.isArray(modelIds)) return '-';
return modelIds.map(id => getModelName(id)).join(', ');
}
// 显示创建表单页面
window.showCreateModal = function(apiType) {
if (apiType === 'fine-tune') {
window.location.href = 'fine-tune-create.html';
} else if (apiType === 'model-manage') {
window.location.href = 'model-manage-create.html';
} else if (apiType === 'model-eval') {
window.location.href = 'model-eval-create.html';
} else if (apiType === 'dataset-manage') {
window.location.href = 'dataset-create.html';
} else if (apiType === 'model-compare') {
window.location.href = 'model-compare-create.html';
} else {
window.showMessage('提示', '该功能开发中...', 'info');
}
};
// 返回列表页
window.goBack = function() {
if (window.currentParentPage) {
window.currentPage = window.currentParentPage;
window.currentParentPage = null;
loadPage(window.currentPage);
} else {
loadPage('fine-tune');
}
};
// 跳转到页面
window.navigateToPage = function(pageName) {
if (pageName.endsWith('-create')) {
window.location.href = `${pageName}.html`;
} else {
window.location.href = `main.html?page=${pageName}`;
}
};
// 返回到列表页
window.goBackToList = function() {
navigateToPage('fine-tune');
};
// 加载页面内容
async function loadPage(pageName) {
// 切换页面时清除选中状态
TableComponent.clearSelection();
// 离开日志页面时停止自动刷新
SystemService.stopLogAutoRefresh();
// 离开模型调优页面时停止进度刷新
if (window.currentPage === 'fine-tune' && pageName !== 'fine-tune') {
TrainingService.stopProgressRefresh();
}
const container = document.getElementById('page-content');
const config = window.tableConfigs[pageName];
if (!config) return;
// 更新当前页面
window.currentPage = pageName;
// 显示加载中
container.innerHTML = `
<div class="bg-white rounded-lg shadow-sm mb-6 p-8 text-center">
<i class="fa fa-spinner fa-spin text-3xl text-primary"></i>
<p class="mt-2 text-gray-500">加载中...</p>
</div>
`;
// 显示/隐藏返回按钮
const backBtn = document.getElementById('pageBackBtn');
if (config.isExternalPage) {
backBtn.classList.remove('hidden');
} else {
backBtn.classList.add('hidden');
}
try {
if (config.isExternalPage) {
// 外部页面
const response = await fetch(`${pageName}.html?t=${Date.now()}`);
if (response.ok) {
const html = await response.text();
const scriptRegex = /<script\b(?![^>]*\bsrc)[^>]*>([\s\S]*?)<\/script>/g;
const scriptContents = [];
let match;
while ((match = scriptRegex.exec(html)) !== null) {
scriptContents.push(match[1]);
}
const scriptContent = scriptContents.join('\n');
const htmlWithoutScript = html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/g, '');
let headerHtml = '';
if (config.createConfig && config.createConfig.hasCreate) {
headerHtml = `
<div class="bg-white rounded-lg shadow-sm mb-6 p-4 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center space-x-8">
<button class="tab-btn active pb-3 text-sm font-medium flex items-center text-primary" data-tab="tasks" onclick="switchTab(this, 'tasks')">
<i class="fa fa-tasks mr-2"></i>评测任务
</button>
<button class="tab-btn pb-3 text-sm font-medium flex items-center text-gray-500" data-tab="leaderboard" onclick="switchTab(this, 'leaderboard')">
<i class="fa fa-trophy mr-2"></i>排行榜
</button>
<button class="tab-btn pb-3 text-sm font-medium flex items-center text-gray-500" data-tab="dimensions" onclick="switchTab(this, 'dimensions')">
<i class="fa fa-sliders mr-2"></i>评测维度
</button>
</div>
<div id="headerActionButtons" style="min-height: 36px;">
<button onclick="navigateToPage('${config.createConfig.page}')" class="bg-primary text-white px-4 py-2 rounded-lg text-sm hover:bg-primary/90 transition-colors flex items-center">
<i class="fa fa-plus mr-2"></i>${config.createConfig.createText}
</button>
</div>
</div>
<style>
.tab-btn { position: relative; transition: all 0.2s; }
.tab-btn.active { color: #1890ff; }
.tab-btn.active::after { content: ''; position: absolute; bottom: -16px; left: 0; right: 0; height: 2px; background-color: #1890ff; }
.tab-btn:hover:not(.active) { color: #1890ff; }
</style>
`;
}
container.innerHTML = headerHtml + htmlWithoutScript;
if (scriptContent && scriptContent.trim()) {
try {
const oldScript = document.getElementById('externalPageScript');
if (oldScript) oldScript.remove();
const scriptEl = document.createElement('script');
scriptEl.id = 'externalPageScript';
scriptEl.textContent = scriptContent;
document.body.appendChild(scriptEl);
} catch (e) {
console.error('执行脚本失败:', e);
}
}
} else {
throw new Error('页面加载失败');
}
} else if (config.isHardwareMonitor) {
container.innerHTML = PageRenderer.renderConfigPage(config, null);
PageRenderer.initGPUList();
PageRenderer.startRefreshTimer();
} else if (config.isLogViewer) {
container.innerHTML = PageRenderer.renderLogViewerPage(config);
SystemService.initLogViewer();
} else if (config.isForm) {
const data = await TableComponent.fetchData(`${window.API_BASE}/${config.api}`);
container.innerHTML = PageRenderer.renderConfigPage(config, data);
} else if (config.isTools) {
container.innerHTML = PageRenderer.renderToolsPage(config);
} else {
// 模型管理页面根据tab选择不同的API
let apiUrl = `${window.API_BASE}/${config.api}`;
if (config.hasModelTabs) {
if (window.currentModelTab === 'trained') {
apiUrl = `${window.API_BASE}/model-manage/trained-models`;
}
}
let data = await TableComponent.fetchData(apiUrl);
let dataPath = config.dataPath || null;
if (config.hasModelTabs && window.currentModelTab === 'trained') {
dataPath = 'models';
}
if (dataPath && typeof data === 'object' && data !== null) {
data = data[dataPath] || [];
}
window.currentPageData = data;
container.innerHTML = TableComponent.renderTablePage(config, data);
setTimeout(() => {
const modelCells = container.querySelectorAll('.model-name-cell');
modelCells.forEach(cell => {
const modelId = cell.getAttribute('data-model-id');
if (modelId) {
fetchAndUpdateModelName(modelId, cell);
}
});
}, 0);
}
} catch (error) {
console.error('加载数据失败:', error);
container.innerHTML = `
<div class="bg-white rounded-lg shadow-sm mb-6 p-8 text-center">
<i class="fa fa-exclamation-circle text-3xl text-danger"></i>
<p class="mt-2 text-gray-500">加载数据失败,请检查后端服务是否启动</p>
<p class="text-sm text-gray-400 mt-1">${error.message}</p>
</div>
`;
}
}
// 添加评测维度
window.addDimension = function() {
window.location.href = 'model-dimension-create.html';
};
// 删除评测维度
window.deleteDimension = async function(id) {
window.showConfirm('确认删除', '确定要删除此评测维度吗?', async () => {
try {
const response = await fetch(`${window.API_BASE}/dimension/${id}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.code === 0) {
window.showMessage('成功', '删除成功', 'success', () => {
switchTab(document.querySelector('[data-tab="dimensions"]'), 'dimensions');
});
} else {
window.showMessage('错误', result.message || '删除失败', 'error');
}
} catch (error) {
console.error('删除维度失败:', error);
window.showMessage('错误', '删除失败: ' + error.message, 'error');
}
});
};
// 切换 Tab
window.switchTab = function(btn, tabId) {
const parent = btn.parentElement;
parent.querySelectorAll('.tab-btn').forEach(b => {
b.classList.remove('active', 'text-primary');
b.classList.add('text-gray-500');
});
btn.classList.add('active');
btn.classList.remove('text-gray-500');
if (typeof window.switchTabContent === 'function') {
window.switchTabContent(tabId);
}
const btnContainer = document.getElementById('headerActionButtons');
const currentConfig = window.tableConfigs[window.currentPage];
if (btnContainer) {
if (tabId === 'tasks') {
const page = currentConfig?.createConfig?.page || 'model-eval-create';
const text = currentConfig?.createConfig?.createText || '新建评测';
btnContainer.innerHTML = `
<button onclick="navigateToPage('${page}')" class="bg-primary text-white px-4 py-2 rounded-lg text-sm hover:bg-primary/90 transition-colors flex items-center">
<i class="fa fa-plus mr-2"></i>${text}
</button>
`;
} else if (tabId === 'leaderboard') {
btnContainer.innerHTML = '<span class="invisible px-4 py-2 rounded-lg">占位</span>';
} else if (tabId === 'dimensions') {
btnContainer.innerHTML = `
<button onclick="addDimension()" class="bg-primary text-white px-4 py-2 rounded-lg text-sm hover:bg-primary/90 transition-colors flex items-center">
<i class="fa fa-plus mr-2"></i>添加维度
</button>
`;
}
}
};
// ============ 初始化 ============
document.addEventListener('DOMContentLoaded', function() {
// 从 localStorage 加载自定义工具
const savedCustomTools = localStorage.getItem('customTools');
if (savedCustomTools) {
window.tableConfigs['data-generate'].customTools = JSON.parse(savedCustomTools);
}
// 加载模型列表缓存
loadModelListCache();
// 检查URL参数
const urlParams = new URLSearchParams(window.location.search);
const pageParam = urlParams.get('page');
let defaultPage = 'fine-tune';
if (pageParam) {
defaultPage = pageParam;
} else {
const sessionPage = sessionStorage.getItem('lastPage');
const localPage = localStorage.getItem('lastPage');
const savedPage = sessionPage || localPage;
if (savedPage && window.tableConfigs[savedPage]) {
defaultPage = savedPage;
}
}
sessionStorage.setItem('lastPage', defaultPage);
// 加载页面
loadPage(defaultPage);
// 启动系统监控定时器
SystemService.fetchSystemMetrics();
setInterval(SystemService.fetchSystemMetrics, 30000);
// 启动训练进度自动刷新
TrainingService.startProgressRefresh();
// 初始化日志
const path = window.location.pathname;
const pageName = path.split('/').pop().replace('.html', '') || 'main';
webLogger.init(pageName);
webLogger.info('页面加载完成');
});