import { getLogFiles, getLogFileContent, deleteLogFile as apiDeleteLogFile } from '../services/apiClient.js'; import { el } from '../utils/dom.js'; import { showNotification } from '../ui/notify.js'; import { showConfirmDialog } from '../ui/dialog.js'; export function loadLogFiles() { return getLogFiles().then((data) => { const list = document.getElementById('logfiles-list'); list.innerHTML = ''; if (data.length === 0) { list.innerHTML = '
暂无日志文件
'; return; } data.sort((a, b) => new Date(b.modified_time) - new Date(a.modified_time)); const groups = {}; data.forEach((f) => { let type = '自定义训练'; const n = f.filename; if (n.includes('train_pretrain_')) type = 'pretrain'; else if (n.includes('train_sft_')) type = 'sft'; else if (n.includes('train_lora_')) type = 'lora'; else if (n.includes('train_dpo_')) type = 'dpo'; else if (n.includes('train_ppo_')) type = 'ppo'; else if (n.includes('train_grpo_')) type = 'grpo'; else if (n.includes('train_spo_')) type = 'spo'; f.train_type = type; if (!groups[type]) groups[type] = []; groups[type].push(f); }); const order = ['pretrain', 'sft', 'lora', 'dpo', 'ppo', 'grpo', 'spo', '未知']; [...order.filter((t) => groups[t]), ...Object.keys(groups).filter((t) => !order.includes(t))].forEach((t) => { list.appendChild(createTypeGroupWithToggle(t, groups[t])); }); }); } function createTypeGroupWithToggle(trainType, files) { const group = el('div', { class: 'process-type-group' }); const header = el('div', { class: 'process-type-header' }); header.dataset.expanded = 'true'; const title = el('h3', { class: 'process-type-title', text: getTrainTypeDisplayName(trainType) }); const toggle = el('button', { class: 'toggle-btn' }); toggle.innerHTML = '▼'; toggle.onclick = (e) => { e.stopPropagation(); toggleGroup(header); }; header.appendChild(title); header.appendChild(toggle); header.onclick = () => toggleGroup(header); const content = el('div', { class: 'process-type-content' }); files.forEach((f) => addLogFileItemToGroup(content, f)); group.appendChild(header); group.appendChild(content); return group; } function toggleGroup(header) { const expanded = header.dataset.expanded === 'true'; const content = header.nextElementSibling; const toggle = header.querySelector('.toggle-btn'); if (expanded) { header.dataset.expanded = 'false'; content.style.maxHeight = '0'; content.style.overflow = 'hidden'; toggle.innerHTML = '▶'; } else { content.style.overflow = 'hidden'; content.style.maxHeight = 'none'; const h = content.scrollHeight; content.style.maxHeight = '0'; content.offsetHeight; header.dataset.expanded = 'true'; content.style.maxHeight = h + 'px'; setTimeout(() => { content.style.maxHeight = 'none'; content.style.overflow = 'visible'; }, 300); toggle.innerHTML = '▼'; } } function getTrainTypeDisplayName(trainType) { const names = { pretrain: '预训练 (Pretrain)', sft: '全参数监督微调 (SFT - Full)', lora: 'LoRA监督微调 (SFT - Lora)', dpo: '直接偏好优化 (RL - DPO)', ppo: 'PPO', grpo: 'GRPO', spo: 'SPO', }; return names[trainType] || trainType; } function addLogFileItemToGroup(parent, logfile) { const item = el('div', { class: 'process-item' }); item.innerHTML = `