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

754 lines
34 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.currentPage = 'fine-tune';
window.currentParentPage = null;
window.selectedItems = new Set(); // 存储选中的项ID
window.currentPageData = []; // 存储当前页面数据
window.modelListCache = []; // 模型列表缓存
window.currentModelTab = 'config'; // 模型管理页面当前tab: 'config'=配置模型, 'trained'=训练模型
// 获取 API 数据
async function fetchData(url) {
const response = await fetch(url);
const result = await response.json();
if (result.code !== 0) {
throw new Error(result.message || '获取数据失败');
}
return result.data || [];
}
// 删除数据
async function deleteItem(api, id) {
// 如果是我的模型,提示删除合并模型
const confirmMessage = api === 'model-manage/trained-models'
? '确定要删除合并模型吗?权重文件不会删除。'
: '确定要删除这条记录吗?';
window.showConfirm('确认删除', confirmMessage, async () => {
try {
// 如果是我的模型调用删除合并模型的API
if (api === 'model-manage/trained-models') {
const response = await fetch(`${window.API_BASE}/model-manage/trained-models/${id}?type=merged`, {
method: 'DELETE'
});
const result = await response.json();
if (result.code === 0) {
window.showMessage('成功', '删除成功', 'success');
// 清除合并状态缓存
sessionStorage.removeItem('merge_status_' + id);
// 刷新当前页面
clearSelection();
const activeLink = document.querySelector('.nav-link.sidebar-item-active');
if (activeLink && typeof window.loadPage === 'function') {
window.loadPage(activeLink.dataset.page);
}
} else {
window.showMessage('错误', result.message || '删除失败', 'error');
}
} else {
const response = await fetch(`${window.API_BASE}/${api}/${id}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.code === 0) {
// 刷新当前页面
clearSelection(); // 清除选中状态
const activeLink = document.querySelector('.nav-link.sidebar-item-active');
if (activeLink && typeof window.loadPage === 'function') {
window.loadPage(activeLink.dataset.page);
}
} else {
window.showMessage('错误', result.message || '删除失败', 'error');
}
}
} catch (error) {
window.showMessage('错误', '删除失败: ' + error.message, 'error');
}
});
}
// 更新模型用途
async function updateModelPurpose(id, purpose) {
try {
const response = await fetch(`${window.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 && typeof window.loadPage === 'function') {
window.loadPage(activeLink.dataset.page);
}
} else {
window.showMessage('错误', result.message || '更新失败', 'error');
}
} catch (error) {
window.showMessage('错误', '更新失败: ' + error.message, 'error');
}
}
// 切换单个项的选中状态
function toggleItemSelection(id, api) {
if (window.selectedItems.has(id)) {
window.selectedItems.delete(id);
} else {
window.selectedItems.add(id);
}
// 重新渲染当前页面以更新UI
refreshCurrentPage();
}
// 切换全选/取消全选
function toggleSelectAll(checkbox, api) {
// 使用保存的当前页面数据
if (checkbox.checked) {
// 全选当前页面的所有数据(支持 name 或 id
window.currentPageData.forEach(item => window.selectedItems.add(item.name || item.id));
} else {
// 取消全选,移除当前页面所有数据的选中状态
window.currentPageData.forEach(item => window.selectedItems.delete(item.name || item.id));
}
refreshCurrentPage();
}
// 清除所有选中项
function clearSelection() {
window.selectedItems.clear();
refreshCurrentPage();
}
// 批量删除选中的项
function batchDeleteItems(api) {
if (window.selectedItems.size === 0) {
window.showMessage('提示', '请先选择要删除的项', 'warning');
return;
}
window.showConfirm('批量删除', `确定要删除选中的 ${window.selectedItems.size} 条记录吗?`, async () => {
const ids = Array.from(window.selectedItems);
let successCount = 0;
let failCount = 0;
for (const id of ids) {
try {
const response = await fetch(`${window.API_BASE}/${api}/${id}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.code === 0) {
successCount++;
} else {
failCount++;
}
} catch (error) {
failCount++;
}
}
clearSelection();
// 刷新当前页面
const activeLink = document.querySelector('.nav-link.sidebar-item-active');
if (activeLink && typeof window.loadPage === 'function') {
window.loadPage(activeLink.dataset.page);
}
if (failCount === 0) {
window.showMessage('成功', `成功删除 ${successCount} 条记录`, 'success');
} else {
window.showMessage('部分失败', `成功删除 ${successCount} 条,${failCount} 条删除失败`, 'warning');
}
});
}
// 刷新当前页面(重新渲染)
function refreshCurrentPage() {
const activeLink = document.querySelector('.nav-link.sidebar-item-active');
if (activeLink) {
const pageName = activeLink.dataset.page;
const config = window.tableConfigs[pageName];
if (config && (config.hasModelTabs || config.api === 'model-manage' || config.api === 'dataset-manage')) {
const container = document.getElementById('page-content');
if (container && typeof renderTablePage === 'function') {
container.innerHTML = renderTablePage(config, window.currentPageData);
// 恢复复选框状态
updateCheckboxStates();
}
}
}
}
// 更新复选框状态(保持选中状态)
function updateCheckboxStates() {
const checkboxes = document.querySelectorAll('tbody input[type="checkbox"]');
checkboxes.forEach(cb => {
const match = cb.getAttribute('onchange')?.match(/toggleItemSelection\((\d+)/) || cb.getAttribute('onchange')?.match(/toggleItemSelection\(([^,]+)/);
const id = match ? parseInt(match[1]) || match[1] : null;
if (id !== null && window.selectedItems.has(id)) {
cb.checked = true;
cb.closest('tr')?.classList.add('bg-blue-50');
} else {
cb.checked = false;
cb.closest('tr')?.classList.remove('bg-blue-50');
}
});
// 更新批量操作栏的显示状态
const batchActions = document.getElementById('batchActions');
if (batchActions) {
if (window.selectedItems.size > 0) {
batchActions.classList.remove('hidden');
batchActions.querySelector('strong').textContent = window.selectedItems.size;
} else {
batchActions.classList.add('hidden');
}
}
// 更新批量删除按钮
const batchDeleteBtn = document.querySelector('#batchActions button[onclick^="batchDeleteItems"]');
if (batchDeleteBtn) {
if (window.selectedItems.size > 0) {
batchDeleteBtn.innerHTML = `<i class="fa fa-trash mr-1"></i>批量删除 (${window.selectedItems.size})`;
} else {
batchDeleteBtn.innerHTML = `<i class="fa fa-trash mr-1"></i>批量删除 (0)`;
}
}
}
// 编辑数据集
function editItem(api, id) {
if (api === 'dataset-manage') {
// 跳转到数据集创建页面进行编辑
window.location.href = `dataset-create.html?id=${id}`;
} else if (api === 'model-manage') {
// 跳转到模型创建页面进行编辑
window.location.href = `model-manage-create.html?id=${id}`;
} else {
window.showMessage('提示', '编辑功能开发中...', 'info');
}
}
// 下载数据集(打包下载)
function downloadDataset(datasetId) {
const protocol = window.location.protocol;
const hostname = window.location.hostname;
window.open(`${protocol}//${hostname}:7861/api/dataset-manage/download/${datasetId}`, '_blank');
}
// 开始模型对比
async function startCompare(id) {
// 跳转到模型对比聊天页面(通过主框架加载)
window.location.href = `main.html?page=model-compare-chat&id=${id}`;
}
// 筛选表格
function filterTable() {
const searchInput = document.getElementById('tableSearchInput');
if (!searchInput) return;
const keyword = searchInput.value.toLowerCase().trim();
const tbody = document.querySelector('#page-content table tbody');
if (!tbody) return;
const rows = tbody.querySelectorAll('tr');
rows.forEach(row => {
const text = row.querySelector('td')?.textContent?.toLowerCase() || '';
if (keyword === '' || text.includes(keyword)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
}
// 刷新表格数据 - 重新加载当前页面
window.loadTableData = function() {
const activeLink = document.querySelector('.nav-link.sidebar-item-active');
if (activeLink && typeof window.loadPage === 'function') {
window.loadPage(activeLink.dataset.page);
}
};
// 合并模型权重(保留兼容)
window.viewTrainedModel = function(name, method, path) {
if (typeof window.startMerge === 'function') {
window.startMerge(name, method, path);
}
};
// 编辑模型
window.editModel = function(modelId) {
window.location.href = `model-manage-create.html?id=${modelId}`;
};
// 预览数据集
window.previewDataset = function(datasetId) {
window.location.href = `dataset-preview.html?id=${datasetId}`;
};
// 下载数据集
window.downloadDataset = function(datasetId) {
window.open(`${window.API_BASE}/dataset-manage/download/${datasetId}`, '_blank');
};
// 导出模型权重
function exportModel(modelName) {
window.open(`${window.API_BASE}/model-manage/trained-models/${encodeURIComponent(modelName)}/export`, '_blank');
}
// 删除已训练模型的权重
async function deleteTrainedWeight(modelName) {
window.showConfirm('确认删除', `确定要删除模型 "${modelName}" 的权重文件吗?合并模型不受影响。`, async () => {
try {
const response = await fetch(`${window.API_BASE}/model-manage/trained-models/${encodeURIComponent(modelName)}?type=lora`, {
method: 'DELETE'
});
const result = await response.json();
if (result.code === 0) {
// 只显示成功消息,不刷新表格(因为后端可能因为没有权重文件而不返回这条记录)
window.showMessage('成功', '权重已删除', 'success');
// 清除该模型的合并状态缓存,让前端重新从后端获取状态
sessionStorage.removeItem('merge_status_' + modelName);
sessionStorage.removeItem('merge_status_' + modelName + '_time');
} else {
window.showMessage('错误', result.message || '删除失败', 'error');
}
} catch (error) {
console.error('删除权重失败:', error);
window.showMessage('错误', '删除失败: ' + error.message, 'error');
}
});
}
// 切换模型管理tab
function switchModelTab(tab) {
window.currentModelTab = tab;
// 清除选中状态
clearSelection();
// 重新加载模型管理页面
window.loadPage('model-manage');
}
// ========== 渲染函数 ==========
// 渲染表格页面
function renderTablePage(config, data) {
// 如果是模型管理页面根据tab动态决定列和配置
let columns = config.columns || [];
let createButton = '';
let supportsMultiSelect = false;
let currentApi = config.api;
if (config.hasModelTabs) {
if (window.currentModelTab === 'config') {
// 配置模型tab
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') : '-' }
];
createButton = `
<button onclick="showCreateModal('model-manage')" class="bg-primary text-white px-3 py-1.5 rounded text-sm hover:bg-primary/90 transition-colors">
<i class="fa fa-plus mr-1"></i>新建模型
</button>
`;
supportsMultiSelect = true;
currentApi = 'model-manage';
} else {
// 训练模型tab
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') : '-' }
];
supportsMultiSelect = true;
currentApi = 'model-manage/trained-models';
}
} else {
// 非模型管理页面,使用原始配置
columns = config.columns || columns;
createButton = config.api === 'dataset-manage' ? `
<button onclick="showCreateModal('${config.api}')" class="bg-primary text-white px-3 py-1.5 rounded text-sm hover:bg-primary/90 transition-colors">
<i class="fa fa-plus mr-1"></i>创建数据集
</button>
` : (config.api === 'fine-tune' ? `
<button onclick="navigateToPage('fine-tune-create')" class="bg-primary text-white px-3 py-1.5 rounded text-sm hover:bg-primary/90 transition-colors">
<i class="fa fa-plus mr-1"></i>新建调优任务
</button>
` : (config.api === 'model-compare' ? `
<button onclick="navigateToPage('model-compare-create')" class="bg-primary text-white px-3 py-1.5 rounded text-sm hover:bg-primary/90 transition-colors">
<i class="fa fa-plus mr-1"></i>新建对比
</button>
` : ''));
supportsMultiSelect = config.api === 'model-manage' || config.api === 'model-manage/trained-models' || config.api === 'dataset-manage' || config.api === 'fine-tune';
}
// 搜索框
const searchBox = (config.api === 'model-manage' || config.api === 'model-manage/trained-models' || config.api === 'dataset-manage' || config.api === 'fine-tune' || config.hasModelTabs) ? `
<div class="relative">
<input type="text" id="tableSearchInput" placeholder="搜索${config.title}..."
class="w-72 pl-9 pr-3 py-1.5 rounded border border-gray-300 text-sm focus:outline-none focus:border-primary focus:ring-1 focus:border-primary"
oninput="filterTable()">
<i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
</div>
` : '';
// 批量删除按钮(仅当有选中项时显示)
const batchDeleteButton = supportsMultiSelect && window.selectedItems.size > 0 ? `
<button onclick="batchDeleteItems('${currentApi}')" class="bg-red-500 text-white px-4 py-2 rounded text-sm hover:bg-red-600 transition-colors font-medium shadow-sm">
<i class="fa fa-trash mr-1"></i>批量删除 (${window.selectedItems.size})
</button>
` : '';
const hasData = data && data.length > 0;
// 多选列头
const selectAllHeader = supportsMultiSelect ? `
<th class="px-4 py-3 text-center font-medium w-10">
<input type="checkbox" class="w-4 h-4 text-primary rounded border-gray-300 cursor-pointer"
onchange="toggleSelectAll(this, '${currentApi}')">
</th>
` : '';
return `
<div class="bg-white rounded-lg shadow-sm mb-6">
<div class="flex items-center justify-between p-4 border-b border-gray-100">
<h2 class="text-lg font-medium">${config.title}</h2>
<div class="flex items-center space-x-3">
${searchBox}
${createButton}
</div>
</div>
${config.hasModelTabs ? `
<div class="px-4 border-b border-gray-100">
<div class="flex space-x-1" id="modelTabs">
<button onclick="switchModelTab('config')" class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg transition-colors ${window.currentModelTab === 'config' ? 'bg-primary text-white' : 'text-gray-500 hover:text-gray-700 hover:bg-gray-100'}">
<i class="fa fa-cog mr-1"></i>配置模型
</button>
<button onclick="switchModelTab('trained')" class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg transition-colors ${window.currentModelTab === 'trained' ? 'bg-primary text-white' : 'text-gray-500 hover:text-gray-700 hover:bg-gray-100'}">
<i class="fa fa-rocket mr-1"></i>训练模型
</button>
</div>
</div>
<style>
.tab-btn { position: relative; transition: all 0.2s; }
</style>
` : ''}
${supportsMultiSelect ? `
<div id="batchActions" class="px-4 py-2 bg-blue-50 border-b border-blue-100 flex items-center justify-between ${window.selectedItems.size > 0 ? '' : 'hidden'}">
<div class="flex items-center text-sm text-blue-700">
<span>已选择 <strong>${window.selectedItems.size}</strong> 项</span>
<button onclick="clearSelection()" class="ml-3 text-blue-500 hover:text-blue-700 underline">取消选择</button>
</div>
<div class="flex items-center space-x-2">
${batchDeleteButton}
</div>
</div>
` : ''}
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="table-header-bg text-gray-500 text-sm">
${selectAllHeader}
${columns.map(col => `<th class="px-4 py-3 text-center font-medium">${col.title}</th>`).join('')}
<th class="px-4 py-3 text-center font-medium">操作</th>
</tr>
</thead>
<tbody>
${hasData ? data.map(item => `
<tr class="border-b border-gray-100 table-row-hover ${window.selectedItems.has(item.name || item.id) ? 'bg-blue-50' : ''}">
${supportsMultiSelect ? `
<td class="px-4 py-4 text-sm text-center">
<input type="checkbox" class="w-4 h-4 text-primary rounded border-gray-300 cursor-pointer"
${window.selectedItems.has(item.name || item.id) ? 'checked' : ''}
onchange="toggleItemSelection('${item.name || item.id}', '${currentApi}')">
</td>
` : ''}
${columns.map(col => `
<td class="px-4 py-4 text-sm text-center">
${col.render ? col.render(item[col.key], item) : (item[col.key] || '-')}
</td>
`).join('')}
<td class="px-4 py-4 text-sm text-center">
<div class="flex justify-center space-x-2">
${config.api === 'fine-tune' ? `
<button onclick="viewFineTuneLogs('${item.id}', '${item.name}')" class="bg-blue-500 text-white px-3 py-1 rounded text-xs hover:bg-blue-600">查看日志</button>
<button onclick="deleteItem('${config.api}', '${item.id}')" class="bg-red-500 text-white px-3 py-1 rounded text-xs hover:bg-red-600">删除任务</button>
` : (currentApi === 'model-manage/trained-models' ? `
${getMergeButtonHtml(item.name, item.train_methods?.[0]?.name || 'lora', item.base_model_path || '', item.merged, item.merging)}
<button onclick="deleteTrainedWeight('${item.name}')" class="bg-orange-500 text-white px-3 py-1 rounded text-xs hover:bg-orange-600" title="删除权重文件">删除权重</button>
${item.merged ? `
<button onclick="deleteItem('${currentApi}', '${item.name || item.id}')" class="bg-red-500 text-white px-3 py-1 rounded text-xs hover:bg-red-600" title="删除合并模型">删除模型</button>
` : ''}
${(item.merged && !item.merging) ? `
<button onclick="exportModel('${item.name}')" class="bg-green-500 text-white px-3 py-1 rounded text-xs hover:bg-green-600">导出</button>
` : ''}
` : (currentApi === 'model-manage' ? `
<button onclick="editModel('${item.id}')" class="bg-blue-500 text-white px-3 py-1 rounded text-xs hover:bg-blue-600">编辑</button>
<button onclick="deleteItem('${currentApi}', '${item.id}')" class="bg-red-500 text-white px-3 py-1 rounded text-xs hover:bg-red-600">删除</button>
` : (config.api === 'dataset-manage' ? `
<button onclick="previewDataset('${item.id}')" class="bg-blue-500 text-white px-3 py-1 rounded text-xs hover:bg-blue-600">预览</button>
<button onclick="downloadDataset('${item.id}')" class="bg-green-500 text-white px-3 py-1 rounded text-xs hover:bg-green-600">下载</button>
<button onclick="deleteItem('${config.api}', '${item.id}')" class="bg-red-500 text-white px-3 py-1 rounded text-xs hover:bg-red-600">删除</button>
` : (config.api === 'model-compare' ? `
${getCompareButtonHtml(item.id, item.status)}
` : ''))))}
</div>
</td>
</tr>
`).join('') : ''}
</tbody>
</table>
</div>
${!hasData ? `
<div class="p-8 text-center text-gray-400">
<div class="py-12">
<i class="fa fa-inbox text-5xl mb-4 text-gray-300"></i>
<p class="text-gray-500">暂无数据</p>
</div>
</div>
` : ''}
</div>
`;
}
// 获取合并按钮HTML
function getMergeButtonHtml(name, method, path, merged, merging) {
const storageKey = 'merge_status_' + name;
const tempStatus = sessionStorage.getItem(storageKey);
const tempStatusTime = sessionStorage.getItem(storageKey + '_time');
console.log('[DEBUG] getMergeButtonHtml:', name, 'tempStatus:', tempStatus, 'merged:', merged, 'merging:', merging);
// 检查临时状态是否过期超过5分钟视为过期
const now = Date.now();
const statusExpired = tempStatusTime && (now - parseInt(tempStatusTime)) > 5 * 60 * 1000;
// 如果状态过期或无效,清除并视为无状态
if (statusExpired || (tempStatus && !tempStatusTime)) {
sessionStorage.removeItem(storageKey);
sessionStorage.removeItem(storageKey + '_time');
// 继续检查后端状态
} else if (tempStatus === 'merging') {
// 如果后端已经完成合并但前端状态未更新,清除临时状态
if (merged) {
sessionStorage.removeItem(storageKey);
sessionStorage.removeItem(storageKey + '_time');
} else {
return `<button class="bg-gray-300 text-gray-500 px-3 py-1 rounded text-xs cursor-not-allowed flex items-center" disabled>
<i class="fa fa-spinner fa-spin mr-1"></i>合并中...
</button>`;
}
}
// 如果后端返回正在合并中(锁文件存在)
if (merging) {
return `<button class="bg-gray-300 text-gray-500 px-3 py-1 rounded text-xs cursor-not-allowed flex items-center" disabled>
<i class="fa fa-spinner fa-spin mr-1"></i>合并中...
</button>`;
}
if (tempStatus === 'success' && merged) {
sessionStorage.removeItem(storageKey);
sessionStorage.removeItem(storageKey + '_time');
return `<button class="bg-gray-300 text-gray-500 px-3 py-1 rounded text-xs cursor-not-allowed" disabled>合并成功</button>`;
}
if (tempStatus === 'success' && !merged) {
sessionStorage.removeItem(storageKey);
sessionStorage.removeItem(storageKey + '_time');
}
if (merged) {
return `<button class="bg-gray-300 text-gray-500 px-3 py-1 rounded text-xs cursor-not-allowed" disabled>合并成功</button>`;
}
return `<button onclick="startMerge('${name}', '${method}', '${path}')" class="bg-primary text-white px-3 py-1 rounded text-xs hover:bg-primary/90">合并权重</button>`;
}
// 获取模型对比操作按钮HTML
function getCompareButtonHtml(id, status) {
// status: pending(未加载), loading(加载中), loaded(已加载)
if (status === 'loading') {
return `<button class="bg-yellow-500 text-white px-3 py-1 rounded text-xs cursor-not-allowed flex items-center" disabled>
<i class="fa fa-spinner fa-spin mr-1"></i>加载中...
</button>`;
}
if (status === 'loaded') {
return `
<button onclick="startCompare(${id})" class="bg-green-500 text-white px-3 py-1 rounded text-xs hover:bg-green-600 flex items-center">
<i class="fa fa-play mr-1"></i>开始对比
</button>
<button onclick="unloadCompare(${id})" class="bg-gray-500 text-white px-3 py-1 rounded text-xs hover:bg-gray-600">
停止
</button>
`;
}
// pending - 显示"准备加载"按钮
return `<button onclick="loadCompare(${id})" class="bg-primary text-white px-3 py-1 rounded text-xs hover:bg-primary/90">
<i class="fa fa-cloud-download mr-1"></i>准备加载
</button>`;
}
// 加载模型对比任务
async function loadCompare(id) {
try {
const response = await fetch(`${window.API_BASE}/model-compare/${id}/load`, {
method: 'POST'
});
const result = await response.json();
if (result.code === 0) {
window.showMessage('提示', '正在加载模型,请稍候...', 'info');
// 轮询加载状态
pollLoadStatus(id);
} else {
window.showMessage('错误', result.message || '加载失败', 'error');
}
} catch (error) {
console.error('加载模型失败:', error);
window.showMessage('错误', '加载失败: ' + error.message, 'error');
}
}
// 轮询加载状态
async function pollLoadStatus(id, maxAttempts = 60) {
let attempts = 0;
const checkInterval = 3000; // 3秒检查一次
const poll = async () => {
attempts++;
try {
const response = await fetch(`${window.API_BASE}/model-compare/${id}/load-status`);
const result = await response.json();
if (result.code === 0) {
const data = result.data;
if (data.all_ready) {
window.showMessage('成功', '模型加载完成!可以开始对比了。', 'success');
window.loadTableData(); // 刷新表格显示"开始对比"按钮
} else if (attempts < maxAttempts) {
setTimeout(poll, checkInterval);
} else {
window.showMessage('警告', '模型加载超时,请重试', 'warning');
}
} else if (attempts < maxAttempts) {
setTimeout(poll, checkInterval);
} else {
window.showMessage('错误', result.message || '加载状态检查失败', 'error');
}
} catch (error) {
console.error('检查加载状态失败:', error);
if (attempts < maxAttempts) {
setTimeout(poll, checkInterval);
}
}
};
setTimeout(poll, checkInterval);
}
// 停止模型对比任务
async function unloadCompare(id) {
window.showConfirm('确认停止', '确定要停止模型服务吗?', async () => {
try {
const response = await fetch(`${window.API_BASE}/model-compare/${id}/unload`, {
method: 'POST'
});
const result = await response.json();
if (result.code === 0) {
window.showMessage('成功', '已停止模型服务', 'success');
window.loadTableData();
} else {
window.showMessage('错误', result.message || '停止失败', 'error');
}
} catch (error) {
console.error('停止模型服务失败:', error);
window.showMessage('错误', '停止失败: ' + error.message, 'error');
}
});
}
// 启动合并任务
async function startMerge(name, method, path) {
const storageKey = 'merge_status_' + name;
sessionStorage.setItem(storageKey, 'merging');
sessionStorage.setItem(storageKey + '_time', Date.now().toString());
window.loadTableData();
try {
const response = await fetch(`${window.API_BASE}/model-manage/merge`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model_name: name,
train_method: method || 'lora',
base_model_path: path
})
});
const result = await response.json();
if (result.code === 0) {
sessionStorage.setItem(storageKey, 'success');
setTimeout(() => window.loadTableData(), 1500);
} else {
sessionStorage.removeItem(storageKey);
sessionStorage.removeItem(storageKey + '_time');
window.showMessage('失败', result.message || '合并失败', 'error');
window.loadTableData();
}
} catch (error) {
console.error('[DEBUG] 合并失败:', error);
sessionStorage.removeItem(storageKey);
sessionStorage.removeItem(storageKey + '_time');
window.showMessage('错误', '合并失败: ' + error.message, 'error');
window.loadTableData();
}
}
// 导出表格组件
window.TableComponent = {
fetchData,
deleteItem,
updateModelPurpose,
toggleItemSelection,
toggleSelectAll,
clearSelection,
batchDeleteItems,
refreshCurrentPage,
updateCheckboxStates,
editItem,
downloadDataset,
startCompare,
filterTable,
exportModel,
switchModelTab,
renderTablePage,
getMergeButtonHtml,
startMerge,
deleteTrainedWeight,
getCompareButtonHtml,
loadCompare,
unloadCompare
};