Files
YG_FT_Platform/web/js/main.js

598 lines
24 KiB
JavaScript
Raw Normal View History

/**
* 主入口模块
* 页面初始化和导航控制
*/
// 各功能模块的表格配置
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('页面加载完成');
});