minimind/scripts/static/js/script.js

242 lines
9.3 KiB
JavaScript

// 标签切换功能
function openTab(evt, tabName) {
var i, tabContent, tabLinks;
tabContent = document.getElementsByClassName("tab-content");
for (i = 0; i < tabContent.length; i++) {
tabContent[i].classList.add("hidden");
}
tabLinks = document.getElementsByClassName("tab");
for (i = 0; i < tabLinks.length; i++) {
tabLinks[i].classList.remove("active");
}
document.getElementById(tabName).classList.remove("hidden");
evt.currentTarget.classList.add("active");
// 如果切换到进程页面,刷新进程列表
if (tabName === 'processes') {
loadProcesses();
} else if (tabName === 'logfiles') {
loadLogFiles();
}
}
// 根据训练类型显示/隐藏特定参数
document.getElementById('train_type').addEventListener('change', function() {
const trainType = this.value;
const pretrainSftFields = document.querySelectorAll('.pretrain-sft');
const loraFields = document.querySelectorAll('.lora');
pretrainSftFields.forEach(field => {
field.style.display = (trainType === 'pretrain' || trainType === 'sft') ? 'block' : 'none';
});
loraFields.forEach(field => {
field.style.display = trainType === 'lora' ? 'block' : 'none';
});
// 设置默认值
if (trainType === 'pretrain') {
document.getElementById('save_dir').value = '../out';
document.getElementById('save_weight').value = 'pretrain';
document.getElementById('epochs').value = '1';
document.getElementById('batch_size').value = '32';
document.getElementById('learning_rate').value = '5e-4';
document.getElementById('data_path').value = '../dataset/pretrain_hq.jsonl';
document.getElementById('from_weight').value = 'none';
document.getElementById('log_interval').value = '100';
document.getElementById('save_interval').value = '100';
} else if (trainType === 'sft') {
document.getElementById('save_dir').value = '../out';
document.getElementById('save_weight').value = 'full_sft';
document.getElementById('epochs').value = '2';
document.getElementById('batch_size').value = '16';
document.getElementById('learning_rate').value = '5e-7';
document.getElementById('data_path').value = '../dataset/sft_mini_512.jsonl';
document.getElementById('from_weight').value = 'pretrain';
document.getElementById('log_interval').value = '100';
document.getElementById('save_interval').value = '100';
} else if (trainType === 'lora') {
document.getElementById('save_dir').value = '../out/lora';
document.getElementById('lora_name').value = 'lora_identity';
document.getElementById('epochs').value = '50';
document.getElementById('batch_size').value = '32';
document.getElementById('learning_rate').value = '1e-4';
document.getElementById('data_path').value = '../dataset/lora_identity.jsonl';
document.getElementById('from_weight').value = 'full_sft';
document.getElementById('log_interval').value = '10';
document.getElementById('save_interval').value = '1';
}
});
// 初始触发一次change事件以设置默认值
document.getElementById('train_type').dispatchEvent(new Event('change'));
// 加载进程列表
function loadProcesses() {
fetch('/processes')
.then(response => response.json())
.then(data => {
const processList = document.getElementById('process-list');
processList.innerHTML = '';
if (data.length === 0) {
processList.innerHTML = '<p>暂无训练进程</p>';
return;
}
data.forEach(process => {
const processItem = document.createElement('div');
processItem.className = 'process-item';
let statusClass = '';
let statusText = '';
if (process.running) {
statusClass = 'status-running';
statusText = '运行中';
} else if (process.error) {
statusClass = 'status-error';
statusText = '出错';
} else {
statusClass = 'status-completed';
statusText = '已完成';
}
processItem.innerHTML = `
<div class="process-info">
<div>
<strong>${process.train_type}</strong> - ${process.start_time}
</div>
<div>
<span class="process-status ${statusClass}">${statusText}</span>
</div>
</div>
<div>
<button class="btn-logs" onclick="showLogs('${process.id}')">查看日志</button>
${process.running ? `<button class="btn-stop" onclick="stopProcess('${process.id}')">停止训练</button>` : ''}
</div>
<div id="logs-${process.id}" class="logs-container hidden"></div>
`;
processList.appendChild(processItem);
});
});
}
// 显示日志
function showLogs(processId) {
const logsContainer = document.getElementById(`logs-${processId}`);
logsContainer.classList.toggle('hidden');
if (!logsContainer.classList.contains('hidden')) {
fetch(`/logs/${processId}`)
.then(response => response.text())
.then(logs => {
logsContainer.textContent = logs;
logsContainer.scrollTop = logsContainer.scrollHeight;
});
}
}
// 停止进程
function stopProcess(processId) {
if (confirm('确定要停止这个训练进程吗?')) {
fetch(`/stop/${processId}`, {
method: 'POST'
})
.then(() => {
loadProcesses();
});
}
}
// 表单提交处理
document.getElementById('train-form').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const data = Object.fromEntries(formData.entries());
fetch('/train', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.success) {
alert('训练已开始!');
openTab(event, 'processes');
} else {
alert('训练启动失败:' + result.error);
}
});
});
// 加载日志文件列表
function loadLogFiles() {
fetch('/logfiles')
.then(response => response.json())
.then(data => {
const logfilesList = document.getElementById('logfiles-list');
logfilesList.innerHTML = '';
if (data.length === 0) {
logfilesList.innerHTML = '<p>暂无日志文件</p>';
return;
}
// 按日期倒序排序
data.sort((a, b) => new Date(b.modified_time) - new Date(a.modified_time));
data.forEach(logfile => {
const fileItem = document.createElement('div');
fileItem.className = 'process-item';
// 从文件名提取训练类型
let trainType = '未知';
if (logfile.filename.includes('train_pretrain_')) {
trainType = 'pretrain';
} else if (logfile.filename.includes('train_sft_')) {
trainType = 'sft';
} else if (logfile.filename.includes('train_lora_')) {
trainType = 'lora';
}
fileItem.innerHTML = `
<div class="process-info">
<div>
<strong>${trainType}</strong> - ${logfile.modified_time}
</div>
<div>
<span class="process-status status-completed">已保存</span>
</div>
</div>
<div>
<button class="btn-logs" onclick="viewLogFile('${logfile.filename}', this)">查看日志</button>
</div>
<div id="log-content-${logfile.filename.replace(/\./g, '-')}" class="logs-container hidden"></div>
`;
logfilesList.appendChild(fileItem);
});
});
}
// 查看日志文件内容
function viewLogFile(filename, button) {
const safeFilename = filename.replace(/\./g, '-');
const logContainer = button.closest('.process-item').querySelector(`#log-content-${safeFilename}`);
logContainer.classList.toggle('hidden');
if (!logContainer.classList.contains('hidden') && logContainer.textContent === '') {
logContainer.textContent = '加载日志中...';
fetch(`/logfile-content/${encodeURIComponent(filename)}`)
.then(response => response.text())
.then(logs => {
logContainer.textContent = logs;
logContainer.scrollTop = 0;
});
}
}