diff --git a/web/pages/hardware.html b/web/pages/hardware.html
new file mode 100644
index 0000000..5e56e2a
--- /dev/null
+++ b/web/pages/hardware.html
@@ -0,0 +1,479 @@
+
+
+
+
-
+
其他工具
@@ -242,13 +93,13 @@
-
+
查看日志
@@ -333,58 +184,6 @@
}
const API_BASE = window.API_BASE;
- // 日志自动刷新相关变量
- let logRefreshTimer = null;
- let logCountdownTimer = null;
- let logCurrentInterval = 10;
- let logFullContent = ''; // 存储完整日志内容
-
- // 设置自动刷新间隔
- function setRefreshInterval() {
- const select = document.getElementById('logRefreshInterval');
- const countdownEl = document.getElementById('logRefreshCountdown');
- const secondsEl = document.getElementById('countdownNumber');
-
- if (!select) return;
-
- logCurrentInterval = parseInt(select.value) || 10;
-
- // 清除之前的定时器
- if (logRefreshTimer) {
- clearInterval(logRefreshTimer);
- logRefreshTimer = null;
- }
- if (logCountdownTimer) {
- clearInterval(logCountdownTimer);
- logCountdownTimer = null;
- }
-
- // 如果选择关闭,不显示倒计时
- if (select.value === '0') {
- countdownEl.classList.add('hidden');
- return;
- }
-
- // 显示倒计时
- countdownEl.classList.remove('hidden');
- secondsEl.textContent = logCurrentInterval;
-
- // 启动倒计时
- let countdown = logCurrentInterval;
- logCountdownTimer = setInterval(() => {
- countdown--;
- if (countdown <= 0) {
- countdown = logCurrentInterval;
- }
- secondsEl.textContent = countdown;
- }, 1000);
-
- // 启动自动刷新
- logRefreshTimer = setInterval(() => {
- refreshLogs();
- }, logCurrentInterval * 1000);
- }
-
// 获取系统性能监控数据
async function fetchSystemMetrics() {
try {
@@ -513,18 +312,15 @@
},
'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: []
+ skipFetch: true,
+ hasCreate: false,
+ isExternalPage: true
},
'model-manage': {
title: '模型管理',
api: 'model-manage',
hasCreate: true,
+ hasModelTabs: true,
createText: '添加模型',
columns: [
{ title: '模型名称', key: 'name' },
@@ -562,17 +358,11 @@
],
actions: ['edit', 'delete']
},
- 'config': {
- title: '平台性能',
- skipFetch: true,
- hasCreate: false,
- isHardwareMonitor: true
- },
'logs': {
title: '查看日志',
skipFetch: true,
hasCreate: false,
- isLogViewer: true
+ isExternalPage: true
},
'model-compare-chat': {
title: '模型对比',
@@ -799,6 +589,12 @@
// 绑定导航点击事件
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', function(e) {
+ // 如果是外部页面链接(直接 href 到 .html),允许默认行为
+ const href = this.getAttribute('href');
+ if (href && href.endsWith('.html')) {
+ return; // 允许浏览器默认跳转
+ }
+
e.preventDefault();
const page = this.dataset.page;
loadPage(page);
@@ -854,9 +650,6 @@
// 切换页面时清除选中状态
clearSelection();
- // 离开日志页面时停止自动刷新
- stopLogAutoRefresh();
-
// 离开模型调优页面时停止进度刷新
if (currentPage === 'fine-tune' && pageName !== 'fine-tune') {
if (progressRefreshTimer) {
@@ -955,20 +748,18 @@
} else {
throw new Error('页面加载失败');
}
- } else if (config.isHardwareMonitor) {
- // 硬件监控页面使用模拟数据,不调用API
- container.innerHTML = renderConfigPage(config, null);
- } else if (config.isLogViewer) {
- // 日志查看页面
- container.innerHTML = renderLogViewerPage(config);
- initLogViewer();
} else if (config.isForm) {
const data = await fetchData(`${API_BASE}/${config.api}`);
- container.innerHTML = renderConfigPage(config, data);
- } else if (config.isTools) {
- container.innerHTML = renderToolsPage(config);
+ container.innerHTML = renderFormPage(config, data);
} else {
- let data = await fetchData(`${API_BASE}/${config.api}`);
+ // 模型管理页面根据tab选择不同的API
+ let apiUrl = `${API_BASE}/${config.api}`;
+ if (config.hasModelTabs) {
+ if (currentModelTab === 'trained') {
+ apiUrl = `${API_BASE}/model-manage/trained-models`;
+ }
+ }
+ let data = await fetchData(apiUrl);
// 如果配置了 dataPath,从返回数据中提取指定字段
if (config.dataPath && typeof data === 'object' && data !== null) {
data = data[config.dataPath] || [];
@@ -989,11 +780,22 @@
}
} catch (error) {
console.error('加载数据失败:', error);
+ // 详细的错误分类
+ let errorMsg = error.message || '未知错误';
+ let errorHint = '请检查后端服务是否启动';
+
+ if (errorMsg.includes('页面加载失败')) {
+ errorHint = '页面文件不存在,请检查路由配置';
+ } else if (errorMsg.includes('获取数据失败') || errorMsg.includes('Failed to fetch')) {
+ errorHint = 'API 服务不可用,请检查后端服务是否启动';
+ }
+
container.innerHTML = `
-
加载数据失败,请检查后端服务是否启动
-
${error.message}
+
加载数据失败
+
${errorMsg}
+
${errorHint}
`;
}
@@ -1210,10 +1012,14 @@
const pageName = activeLink.dataset.page;
const config = tableConfigs[pageName];
- if (config.api === 'model-manage' || config.api === 'dataset-manage') {
- document.getElementById('page-content').innerHTML = renderTablePage(config, currentPageData);
- // 恢复复选框状态
- updateCheckboxStates();
+ // 检查 config 和 api 是否存在
+ if (config && (config.api === 'model-manage' || config.api === 'dataset-manage' || config.hasModelTabs)) {
+ const container = document.getElementById('page-content');
+ if (container && typeof renderTablePage === 'function') {
+ container.innerHTML = renderTablePage(config, currentPageData);
+ // 恢复复选框状态
+ updateCheckboxStates();
+ }
}
}
}
@@ -1364,10 +1170,20 @@
${createButton}
- ${config.api === 'model-manage/trained-models' ? `
+ ${config.hasModelTabs ? `
@@ -1442,1006 +1258,17 @@
`;
}
- // 渲染日志查看页面
- function renderLogViewerPage(config) {
- return `
-
-
-
${config.title}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 下次刷新: 10秒
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `;
- }
-
- // 当前日志类型:system 或 training
- let currentLogTab = 'system';
-
- // 切换日志类型标签
- function switchLogTab(tab) {
- currentLogTab = tab;
- const systemTab = document.getElementById('logTabSystem');
- const trainingTab = document.getElementById('logTabTraining');
- const systemOptions = document.getElementById('systemLogOptions');
- const trainingOptions = document.getElementById('trainingLogOptions');
-
- if (tab === 'system') {
- systemTab.className = 'px-4 py-1.5 text-sm rounded-md transition-colors bg-white shadow-sm text-primary';
- trainingTab.className = 'px-4 py-1.5 text-sm rounded-md transition-colors text-gray-600 hover:text-gray-800';
- systemOptions.classList.remove('hidden');
- trainingOptions.classList.add('hidden');
- loadLogFiles();
- } else {
- trainingTab.className = 'px-4 py-1.5 text-sm rounded-md transition-colors bg-white shadow-sm text-primary';
- systemTab.className = 'px-4 py-1.5 text-sm rounded-md transition-colors text-gray-600 hover:text-gray-800';
- trainingOptions.classList.remove('hidden');
- systemOptions.classList.add('hidden');
- loadTrainingLogFiles();
- }
- }
-
- // 初始化日志查看器
- function initLogViewer() {
- const datePicker = document.getElementById('logDatePicker');
- if (datePicker) {
- const today = new Date().toISOString().split('T')[0];
- datePicker.value = today;
- }
- // 加载默认日志类型
- loadLogFiles();
- // 启动自动刷新
- setRefreshInterval();
- }
-
- // 加载训练日志文件列表
- async function loadTrainingLogFiles() {
- const logSelect = document.getElementById('trainingLogSelect');
- if (!logSelect) return;
-
- logSelect.innerHTML = '
';
-
- try {
- const response = await fetch(`${API_BASE}/training-log-files`);
- const result = await response.json();
-
- if (result.code === 0 && result.data) {
- logSelect.innerHTML = '
';
- result.data.forEach(log => {
- const option = document.createElement('option');
- option.value = log.file;
- option.textContent = `${log.name} (PID: ${log.pid}, ${log.date}, ${log.size})`;
- logSelect.appendChild(option);
- });
- // 如果有日志文件,自动加载第一个
- if (result.data.length > 0) {
- logSelect.value = result.data[0].file;
- loadSelectedTrainingLog();
- } else {
- document.getElementById('logContent').textContent = '暂无训练日志';
- document.getElementById('logFileInfo').textContent = '无训练日志';
- }
- } else {
- logSelect.innerHTML = '
';
- document.getElementById('logContent').textContent = '暂无训练日志';
- document.getElementById('logFileInfo').textContent = '无训练日志';
- }
- } catch (error) {
- console.error('加载训练日志列表失败:', error);
- logSelect.innerHTML = '
';
- document.getElementById('logContent').textContent = '加载训练日志列表失败: ' + error.message;
- }
- }
-
- // 加载选中的训练日志
- async function loadSelectedTrainingLog() {
- const logSelect = document.getElementById('trainingLogSelect');
- const logFile = logSelect.value;
- const logContent = document.getElementById('logContent');
- const logFileInfo = document.getElementById('logFileInfo');
-
- if (!logFile) {
- logContent.textContent = '请选择训练日志';
- logFileInfo.textContent = '无训练日志';
- return;
- }
-
- logContent.textContent = '加载中...';
- logFileInfo.textContent = '加载中...';
-
- try {
- const response = await fetch(`${API_BASE}/training-log-content?file=${encodeURIComponent(logFile)}`);
- const result = await response.json();
-
- if (result.code === 0 && result.data) {
- logFullContent = result.data.content || '';
- logContent.textContent = logFullContent || '(空日志)';
- logFileInfo.textContent = result.data.file + ' (' + result.data.size + ')';
- // 清空搜索
- document.getElementById('logSearchInput').value = '';
- document.getElementById('logMatchCount').textContent = '';
- // 滚动到底部
- scrollToLogBottom();
- } else {
- logContent.textContent = '加载失败: ' + (result.message || '未知错误');
- logFileInfo.textContent = '加载失败';
- }
- } catch (error) {
- console.error('加载训练日志内容失败:', error);
- logContent.textContent = '加载失败: ' + error.message;
- logFileInfo.textContent = '加载失败';
- }
- }
-
- // 加载日志文件列表
- async function loadLogFiles() {
- const datePicker = document.getElementById('logDatePicker');
- const logTypeSelect = document.getElementById('logTypeSelect');
- const selectedDate = datePicker ? datePicker.value : new Date().toISOString().split('T')[0];
-
- if (!logTypeSelect) return;
- logTypeSelect.innerHTML = '
';
-
- try {
- const response = await fetch(`${API_BASE}/log-files?date=${selectedDate}`);
- const result = await response.json();
-
- if (result.code === 0 && result.data) {
- logTypeSelect.innerHTML = '
';
- result.data.forEach(log => {
- const option = document.createElement('option');
- option.value = log.file;
- option.textContent = log.name + ' (' + log.size + ')';
- logTypeSelect.appendChild(option);
- });
- // 如果有日志文件,自动加载第一个
- if (result.data.length > 0) {
- logTypeSelect.value = result.data[0].file;
- loadSelectedLog();
- } else {
- logTypeSelect.innerHTML = '
';
- document.getElementById('logContent').textContent = '该日期暂无日志文件';
- document.getElementById('logFileInfo').textContent = '无日志文件';
- }
- } else {
- logTypeSelect.innerHTML = '
';
- document.getElementById('logContent').textContent = '该日期暂无日志文件';
- document.getElementById('logFileInfo').textContent = '无日志文件';
- }
- } catch (error) {
- console.error('加载日志文件列表失败:', error);
- logTypeSelect.innerHTML = '
';
- document.getElementById('logContent').textContent = '加载日志文件列表失败: ' + error.message;
- }
- }
-
- // 加载选中的日志
- async function loadSelectedLog() {
- const logTypeSelect = document.getElementById('logTypeSelect');
- const logFile = logTypeSelect.value;
- const logContent = document.getElementById('logContent');
- const logFileInfo = document.getElementById('logFileInfo');
-
- if (!logFile) {
- logContent.textContent = '请选择日志文件';
- logFileInfo.textContent = '无日志文件';
- return;
- }
-
- logContent.textContent = '加载中...';
- logFileInfo.textContent = '加载中...';
-
- try {
- const response = await fetch(`${API_BASE}/log-content?file=${encodeURIComponent(logFile)}`);
- const result = await response.json();
-
- if (result.code === 0) {
- logFullContent = result.data.content || '';
- logContent.textContent = logFullContent || '(空日志)';
- logFileInfo.textContent = result.data.file + ' (' + result.data.size + ')';
- // 清空搜索框和匹配计数
- document.getElementById('logSearchInput').value = '';
- document.getElementById('logMatchCount').textContent = '';
- // 滚动到最底部
- scrollToLogBottom();
- } else {
- logContent.textContent = '加载失败: ' + (result.message || '未知错误');
- logFileInfo.textContent = '加载失败';
- }
- } catch (error) {
- console.error('加载日志内容失败:', error);
- logContent.textContent = '加载日志内容失败: ' + error.message;
- logFileInfo.textContent = '加载失败';
- }
- }
-
- // 刷新日志
- function refreshLogs() {
- if (currentLogTab === 'system') {
- loadLogFiles();
- if (document.getElementById('logTypeSelect').value) {
- loadSelectedLog();
- }
- } else {
- loadTrainingLogFiles();
- }
- // 重置倒计时
- const select = document.getElementById('logRefreshInterval');
- const secondsEl = document.getElementById('countdownNumber');
- if (select && select.value !== '0' && secondsEl) {
- secondsEl.textContent = logCurrentInterval;
- }
- }
-
- // 停止日志自动刷新(离开页面时调用)
- function stopLogAutoRefresh() {
- if (logRefreshTimer) {
- clearInterval(logRefreshTimer);
- logRefreshTimer = null;
- }
- if (logCountdownTimer) {
- clearInterval(logCountdownTimer);
- logCountdownTimer = null;
- }
- }
-
- // 滚动到日志底部
- function scrollToLogBottom() {
- const logContent = document.getElementById('logContent');
- if (logContent) {
- logContent.scrollTop = logContent.scrollHeight;
- }
- }
-
- // 过滤日志内容
- function filterLogContent() {
- const searchInput = document.getElementById('logSearchInput');
- const matchCount = document.getElementById('logMatchCount');
- const logContent = document.getElementById('logContent');
-
- if (!searchInput || !matchCount || !logContent) return;
-
- const keyword = searchInput.value.trim();
-
- if (!keyword) {
- logContent.textContent = logFullContent || '(空日志)';
- matchCount.textContent = '';
- scrollToLogBottom();
- return;
- }
-
- const lines = logFullContent.split('\n');
- const matchingLines = lines.filter(line => line.toLowerCase().includes(keyword.toLowerCase()));
-
- if (matchingLines.length > 0) {
- logContent.textContent = matchingLines.join('\n');
- matchCount.textContent = `(${matchingLines.length}条匹配)`;
- // 滚动到最底部查看最新匹配
- scrollToLogBottom();
- } else {
- logContent.textContent = '未找到匹配的日志';
- matchCount.textContent = '(0条匹配)';
- }
- }
-
- // 清空日志内容显示
- function clearLogContent() {
- document.getElementById('logContent').textContent = '日志内容将在这里显示...';
- document.getElementById('logFileInfo').textContent = '请选择日志文件';
- document.getElementById('logTypeSelect').value = '';
- document.getElementById('logSearchInput').value = '';
- document.getElementById('logMatchCount').textContent = '';
- logFullContent = '';
+ // 切换模型管理tab
+ function switchModelTab(tab) {
+ currentModelTab = tab;
+ // 清除选中状态
+ clearSelection();
+ // 重新加载模型管理页面
+ loadPage('model-manage');
}
// 渲染工具卡片页面
- function renderToolsPage(config) {
- // 渲染单个工具卡片
- const renderToolCard = (tool, canDelete = false, isCustom = false) => `
-
-
-
-
-
${tool.name}
-
${tool.description}
- ${canDelete ? `
-
-
- ` : ''}
-
- `;
- const defaultCards = config.defaultTools.map(t => renderToolCard(t, false, false)).join('');
- const customCards = config.customTools.map(t => renderToolCard(t, true, true)).join('');
-
- return `
-
-
-
${config.title}
-
-
-
-
-
默认工具
-
- ${defaultCards || '
暂无默认工具
'}
-
-
-
-
自定义工具
-
- ${customCards || '
暂无自定义工具,点击右上角添加
'}
-
-
-
- `;
- }
-
- // 删除自定义工具
- function deleteCustomTool(toolId) {
- showConfirm('确认删除', '确定要删除这个自定义工具吗?', () => {
- const config = tableConfigs['data-generate'];
- config.customTools = config.customTools.filter(t => t.id !== toolId);
- // 保存到 localStorage
- localStorage.setItem('customTools', JSON.stringify(config.customTools));
- document.getElementById('page-content').innerHTML = renderToolsPage(config);
- // 删除成功,无需额外弹窗提示
- });
- }
-
- // 修改自定义工具
- function editCustomTool(toolId) {
- const config = tableConfigs['data-generate'];
- const tool = config.customTools.find(t => t.id === toolId);
- if (tool) {
- // 将工具信息存储到 localStorage,编辑页面读取
- localStorage.setItem('editTool', JSON.stringify(tool));
- window.location.href = 'custom-tool-create.html?edit=true';
- }
- }
-
- // 显示创建工具弹窗
- function showCreateToolModal() {
- window.location.href = 'custom-tool-create.html';
- }
-
- // 跳转到工具页面
- function navigateToTool(toolId, url, isCustom = false) {
- if (isCustom && url) {
- // 自定义工具,使用配置的URL
- if (url.startsWith('http')) {
- window.open(url, '_blank');
- } else {
- window.location.href = url;
- }
- } else if (toolId === 'data-generate') {
- window.location.href = 'data-generate.html';
- } else if (toolId === 'json2jsonl') {
- showMessage('提示', 'JSON转JSONL 工具开发中...', 'info');
- } else if (toolId === 'md-convert') {
- showMessage('提示', '转换Markdown 工具开发中...', 'info');
- } else {
- showMessage('提示', `${toolId} 功能开发中...`, 'info');
- }
- }
-
- // 渲染配置页面(硬件监控)
- function renderConfigPage(config, data) {
- return `
-
-
-
${config.title}
-
-
- 刷新频率:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 总流入: 0 GB
- 总流出: 0 GB
-
-
-
-
-
-
-
-
- 操作系统
- Ubuntu 22.04 LTS
-
-
- 运行时间
- 0 天 0 时 0 分
-
-
- 进程数
- 0
-
-
- 负载均值
- 0.00, 0.00, 0.00
-
-
-
-
-
-
- `;
- }
-
- // 刷新间隔定时器
- let refreshTimer = null;
- let currentRefreshInterval = 5000;
-
- // 刷新硬件信息(使用真实API)
- async function refreshHardwareInfo() {
- try {
- const response = await fetch(`${API_BASE}/system-info`);
- const result = await response.json();
-
- if (result.code === 0 && result.data) {
- const data = result.data;
-
- // 更新CPU
- const cpu = data.cpu || {};
- const cpuPercent = cpu.percent || 0;
- document.getElementById('cpuPercent').textContent = cpuPercent + '%';
- document.getElementById('cpuBar').style.width = cpuPercent + '%';
- document.getElementById('cpuCores').textContent = (cpu.cores || 0) + ' 核心';
-
- // 更新内存
- const mem = data.memory || {};
- const memUsed = mem.used_gb || 0;
- const memTotal = mem.total_gb || 0;
- const memPercent = mem.percent || 0;
- document.getElementById('memoryPercent').textContent = memPercent + '%';
- document.getElementById('memoryBar').style.width = memPercent + '%';
- document.getElementById('memoryUsed').textContent = memUsed + ' GB';
- document.getElementById('memoryAvailable').textContent = (mem.available_gb || 0) + ' GB';
- document.getElementById('memoryCached').textContent = (mem.cached_gb || 0) + ' GB';
-
- // 更新磁盘
- const disk = data.disk || {};
- const diskUsed = disk.used_gb || 0;
- const diskTotal = disk.total_gb || 0;
- const diskPercent = disk.percent || 0;
- document.getElementById('diskPercent').textContent = diskPercent + '%';
- document.getElementById('diskBar').style.width = diskPercent + '%';
- document.getElementById('diskUsed').textContent = diskUsed + ' GB';
- document.getElementById('diskAvailable').textContent = (diskTotal - diskUsed) + ' GB';
-
- // 更新网络
- const net = data.network || {};
- document.getElementById('totalDownload').textContent = (net.download_mb || 0) + ' GB';
- document.getElementById('totalUpload').textContent = (net.upload_mb || 0) + ' GB';
-
- // 更新系统信息
- const sys = data.system || {};
- const uptime = sys.uptime_seconds || 0;
- const days = Math.floor(uptime / 86400);
- const hours = Math.floor((uptime % 86400) / 3600);
- const mins = Math.floor((uptime % 3600) / 60);
- document.getElementById('uptime').textContent = days + ' 天 ' + hours + ' 时 ' + mins + ' 分';
- document.getElementById('processCount').textContent = sys.process_count || 0;
-
- // 更新GPU信息(传入真实数据)
- updateGPUInfo(data.gpu || []);
- }
- } catch (error) {
- console.error('获取系统信息失败:', error);
- // 如果API调用失败,使用模拟数据作为后备
- useMockData();
- }
- }
-
- // 使用模拟数据(当API不可用时)
- function useMockData() {
- // 更新CPU
- const cpuUsage = Math.floor(Math.random() * 30) + 20;
- document.getElementById('cpuPercent').textContent = cpuUsage + '%';
- document.getElementById('cpuBar').style.width = cpuUsage + '%';
- document.getElementById('core1').textContent = Math.floor(Math.random() * 40 + 20) + '%';
- document.getElementById('core2').textContent = Math.floor(Math.random() * 40 + 15) + '%';
- document.getElementById('core3').textContent = Math.floor(Math.random() * 40 + 25) + '%';
- document.getElementById('core4').textContent = Math.floor(Math.random() * 40 + 10) + '%';
-
- // 更新内存
- const memUsed = (Math.random() * 4 + 6).toFixed(1);
- const memTotal = 16;
- const memPercent = Math.floor((memUsed / memTotal) * 100);
- document.getElementById('memoryPercent').textContent = memPercent + '%';
- document.getElementById('memoryBar').style.width = memPercent + '%';
- document.getElementById('memoryUsed').textContent = memUsed + ' GB';
- document.getElementById('memoryAvailable').textContent = (memTotal - memUsed).toFixed(1) + ' GB';
- document.getElementById('memoryCached').textContent = (Math.random() * 3 + 1).toFixed(1) + ' GB';
-
- // 更新磁盘
- const diskUsed = Math.floor(Math.random() * 100 + 150);
- const diskTotal = 512;
- const diskPercent = Math.floor((diskUsed / diskTotal) * 100);
- document.getElementById('diskPercent').textContent = diskPercent + '%';
- document.getElementById('diskBar').style.width = diskPercent + '%';
- document.getElementById('diskUsed').textContent = diskUsed + ' GB';
- document.getElementById('diskAvailable').textContent = (diskTotal - diskUsed) + ' GB';
- document.getElementById('diskSpeed').textContent = (Math.random() * 500 + 100).toFixed(0) + ' MB/s';
-
- // 更新多GPU信息
- updateGPUInfo();
-
- // 更新网络
- document.getElementById('downloadSpeed').textContent = (Math.random() * 100 + 10).toFixed(1) + ' MB/s';
- document.getElementById('uploadSpeed').textContent = (Math.random() * 50 + 5).toFixed(1) + ' MB/s';
- document.getElementById('totalDownload').textContent = (Math.random() * 500 + 100).toFixed(1) + ' GB';
- document.getElementById('totalUpload').textContent = (Math.random() * 200 + 50).toFixed(1) + ' GB';
-
- // 更新系统信息
- const days = Math.floor(Math.random() * 30);
- const hours = Math.floor(Math.random() * 24);
- const mins = Math.floor(Math.random() * 60);
- document.getElementById('uptime').textContent = days + ' 天 ' + hours + ' 时 ' + mins + ' 分';
- document.getElementById('processCount').textContent = Math.floor(Math.random() * 200 + 100);
- document.getElementById('loadAvg').textContent = (Math.random() * 2).toFixed(2) + ', ' + (Math.random() * 1.5).toFixed(2) + ', ' + (Math.random() * 1).toFixed(2);
- }
-
- // GPU配置 - 支持模拟1-8块GPU
- const GPU_COUNT = 4; // 可配置GPU数量
- const gpuConfigs = [
- { name: 'NVIDIA RTX 3090', memory: 24 },
- { name: 'NVIDIA RTX 4090', memory: 24 },
- { name: 'NVIDIA A100', memory: 80 },
- { name: 'NVIDIA V100', memory: 32 },
- { name: 'NVIDIA T4', memory: 16 },
- { name: 'NVIDIA L40S', memory: 48 },
- { name: 'NVIDIA H100', memory: 80 },
- { name: 'NVIDIA RTX 4080', memory: 16 }
- ];
-
- // 初始化GPU列表(获取真实数据)
- async function initGPUList() {
- try {
- const response = await fetch(`${API_BASE}/system-info`);
- const result = await response.json();
- const gpuData = (result.data && result.data.gpu) || [];
- updateGPUInfo(gpuData);
- } catch (error) {
- console.error('初始化GPU列表失败:', error);
- useMockGPUData();
- }
- }
-
- // 更新GPU信息
- function updateGPUInfo(gpuData) {
- // 如果有真实数据,使用真实数据
- if (gpuData && gpuData.length > 0) {
- const gpuCount = gpuData.length;
- document.getElementById('gpuCount').textContent = `检测到 ${gpuCount} 块 GPU`;
-
- let totalUsedMemory = 0;
- let totalMemory = 0;
-
- // 重新初始化GPU列表
- const gpuList = document.getElementById('gpuList');
- if (gpuList) {
- let gpuCardsHTML = '';
- for (let i = 0; i < gpuCount; i++) {
- const gpu = gpuData[i];
- totalUsedMemory += gpu.memory_used_gb;
- totalMemory += gpu.memory_total_gb;
-
- gpuCardsHTML += `
-
-
-
-
- ${gpu.gpu_percent}%
-
-
-
-
-
-
显存
-
${gpu.memory_used_gb}/${gpu.memory_total_gb} GB
-
-
-
温度
-
${gpu.temperature}°C
-
-
-
功耗
-
${gpu.power_w} W
-
-
-
Fan
-
${gpu.fan_speed || 0}%
-
-
-
-
Clock: ${gpu.clock_mhz || 0} MHz
-
Driver: ${gpu.driver_version || '-'}
-
-
- `;
- }
- gpuList.innerHTML = gpuCardsHTML;
- }
-
- // 更新总显存
- const gpuTotalMem = document.getElementById('gpuTotalMemory');
- if (gpuTotalMem) {
- gpuTotalMem.textContent = `${totalUsedMemory}/${totalMemory} GB`;
- }
- return;
- }
-
- // 没有真实数据,使用模拟数据
- useMockGPUData();
- }
-
- // 使用模拟GPU数据
- function useMockGPUData() {
- const gpuCount = Math.min(GPU_COUNT, 8);
- let totalUsedMemory = 0;
- let totalMemory = 0;
-
- // 重新初始化GPU列表
- const gpuList = document.getElementById('gpuList');
- if (gpuList) {
- let gpuCardsHTML = '';
- for (let i = 0; i < gpuCount; i++) {
- const config = gpuConfigs[i % gpuConfigs.length];
- const gpuUsage = Math.floor(Math.random() * 60 + 20);
- const memUsed = (Math.random() * config.memory * 0.7 + config.memory * 0.1).toFixed(1);
- const temp = Math.floor(Math.random() * 30 + 40);
- const power = Math.floor(Math.random() * 150 + 100);
- const fan = Math.floor(gpuUsage + Math.random() * 10);
-
- totalUsedMemory += parseFloat(memUsed);
- totalMemory += config.memory;
-
- gpuCardsHTML += `
-
-
-
-
-
-
-
-
${config.name}
-
PCIe ${Math.floor(Math.random() * 4 + 1)}:00.0
-
-
-
- ${gpuUsage}%
-
-
-
-
-
-
显存
-
${parseFloat(memUsed).toFixed(1)}/${config.memory} GB
-
-
-
-
-
-
- `;
- }
- gpuList.innerHTML = gpuCardsHTML;
- document.getElementById('gpuCount').textContent = `检测到 ${gpuCount} 块 GPU`;
- }
-
- // 更新总显存
- const gpuTotalMem = document.getElementById('gpuTotalMemory');
- if (gpuTotalMem) {
- gpuTotalMem.textContent = `${totalUsedMemory.toFixed(1)}/${totalMemory} GB`;
- }
- }
-
- // 启动硬件监控自动刷新
- function startRefreshTimer() {
- if (refreshTimer) {
- clearInterval(refreshTimer);
- }
- refreshTimer = setInterval(refreshHardwareInfo, currentRefreshInterval);
- }
-
- // 改变刷新频率
- function changeRefreshRate() {
- const select = document.getElementById('refreshInterval');
- currentRefreshInterval = parseInt(select.value);
- startRefreshTimer();
- }
-
- // 页面加载后初始化并启动定时器
- document.addEventListener('DOMContentLoaded', function() {
- // 只在平台性能页面初始化GPU列表
- const gpuCountEl = document.getElementById('gpuCount');
- if (gpuCountEl) {
- initGPUList();
- startRefreshTimer();
- }
- });
-
- function saveConfig() {
- showMessage('提示', '配置保存功能开发中...', 'info');
- }
// 当前页面状态
let currentPage = 'fine-tune';
@@ -2449,6 +1276,7 @@
let selectedItems = new Set(); // 存储选中的项ID
let currentPageData = []; // 存储当前页面数据
let modelListCache = []; // 模型列表缓存
+ let currentModelTab = 'config'; // 模型管理页面当前tab: 'config'=配置模型, 'trained'=训练模型
// 加载模型列表缓存
async function loadModelListCache() {
@@ -2559,588 +1387,6 @@
}
}
- // 渲染创建训练任务页面
- function renderFineTuneCreatePage() {
- return `
-
-
-
-
- 模型调优
- /
- 创建训练任务
-
-
-
-
- `;
- }
-
- // 绑定创建页面事件
- function bindCreatePageEvents() {
- // 卡片式单选框
- document.querySelectorAll('.card-radio').forEach(card => {
- card.addEventListener('click', () => {
- const parent = card.parentElement;
- parent.querySelectorAll('.card-radio').forEach(c => c.classList.remove('active'));
- card.classList.add('active');
- card.querySelector('input').checked = true;
- });
- });
-
- // 任务名称字数统计
- const nameInput = document.querySelector('input[name="name"]');
- if (nameInput) {
- nameInput.addEventListener('input', () => {
- const countEl = document.getElementById('nameCount');
- if (countEl) countEl.textContent = nameInput.value.length;
- });
- }
-
- // 模型名称字数统计
- const modelNameInput = document.querySelector('input[name="output_model_name"]');
- if (modelNameInput) {
- modelNameInput.addEventListener('input', () => {
- const countEl = document.getElementById('modelNameCount');
- if (countEl) countEl.textContent = modelNameInput.value.length;
- });
- }
-
- // 选择模型后启用训练方法
- const baseModelSelect = document.getElementById('baseModelSelect');
- if (baseModelSelect) {
- baseModelSelect.addEventListener('change', () => {
- const trainMethodLora = document.getElementById('trainMethodLora');
- const trainMethodFull = document.getElementById('trainMethodFull');
- const paramConfigPanelLora = document.getElementById('paramConfigPanelLora');
- const paramConfigPanelFull = document.getElementById('paramConfigPanelFull');
- if (baseModelSelect.value) {
- // 启用训练方法
- trainMethodLora.classList.remove('opacity-50', 'cursor-not-allowed');
- trainMethodFull.classList.remove('opacity-50', 'cursor-not-allowed');
- trainMethodLora.querySelector('input').disabled = false;
- trainMethodFull.querySelector('input').disabled = false;
- } else {
- // 禁用训练方法
- trainMethodLora.classList.add('opacity-50', 'cursor-not-allowed');
- trainMethodFull.classList.add('opacity-50', 'cursor-not-allowed');
- trainMethodLora.querySelector('input').disabled = true;
- trainMethodFull.querySelector('input').disabled = true;
- // 隐藏参数配置面板
- paramConfigPanelLora.classList.add('hidden');
- paramConfigPanelFull.classList.add('hidden');
- // 取消选择训练方法
- document.querySelectorAll('input[name="train_method"]').forEach(input => input.checked = false);
- }
- });
- }
-
- // 训练方法选择后显示/隐藏对应的参数配置面板
- document.querySelectorAll('input[name="train_method"]').forEach(input => {
- input.addEventListener('change', () => {
- const paramConfigPanelLora = document.getElementById('paramConfigPanelLora');
- const paramConfigPanelFull = document.getElementById('paramConfigPanelFull');
- if (input.checked && input.value === 'lora') {
- paramConfigPanelLora.classList.remove('hidden');
- paramConfigPanelFull.classList.add('hidden');
- } else if (input.checked && input.value === 'full') {
- paramConfigPanelLora.classList.add('hidden');
- paramConfigPanelFull.classList.remove('hidden');
- } else {
- paramConfigPanelLora.classList.add('hidden');
- paramConfigPanelFull.classList.add('hidden');
- }
- });
- });
- }
-
- // 加载数据集列表
- async function loadDatasets() {
- try {
- const response = await fetch(`${API_BASE}/dataset-manage`);
- const result = await response.json();
- if (result.code === 0) {
- // 训练集下拉框
- const trainSelect = document.querySelector('select[name="dataset_id"]');
- if (trainSelect) {
- trainSelect.innerHTML = '
' +
- result.data.map(d => `
`).join('');
- }
- // 验证集下拉框
- const validSelect = document.querySelector('select[name="valid_dataset_id"]');
- if (validSelect) {
- validSelect.innerHTML = '
' +
- result.data.map(d => `
`).join('');
- }
- }
- } catch (e) {
- console.error('加载数据集失败:', e);
- }
- }
-
- // 切换验证集面板显示
- function toggleValidSetPanel(type) {
- const autoPanel = document.getElementById('validAutoPanel');
- const customPanel = document.getElementById('validCustomPanel');
- if (type === 'auto') {
- autoPanel.classList.remove('hidden');
- customPanel.classList.add('hidden');
- } else {
- autoPanel.classList.add('hidden');
- customPanel.classList.remove('hidden');
- }
- }
-
- // 恢复默认配置
- function resetDefaultConfig(type) {
- if (type === 'lora') {
- const defaults = {
- 'batch_size_lora': '16',
- 'learning_rate_lora': '3e-4',
- 'n_epochs_lora': '3',
- 'save_steps_lora': '50',
- 'lora_alpha': '32',
- 'lora_dropout': '0.1',
- 'lora_rank': '8',
- 'lr_scheduler_type_lora': 'linear',
- 'max_length_lora': '8192',
- 'warmup_ratio_lora': '0.05',
- 'weight_decay_lora': '0.01'
- };
- for (const [name, value] of Object.entries(defaults)) {
- const input = document.querySelector(`input[name="${name}"]`);
- if (input) input.value = value;
- const select = document.querySelector(`select[name="${name}"]`);
- if (select) select.value = value;
- }
- } else {
- const defaults = {
- 'batch_size_full': '16',
- 'learning_rate_full': '1e-5',
- 'n_epochs_full': '3',
- 'save_steps_full': '50',
- 'lr_scheduler_type_full': 'linear',
- 'max_length_full': '8192',
- 'warmup_ratio_full': '0.05',
- 'weight_decay_full': '0.01'
- };
- for (const [name, value] of Object.entries(defaults)) {
- const input = document.querySelector(`input[name="${name}"]`);
- if (input) input.value = value;
- const select = document.querySelector(`select[name="${name}"]`);
- if (select) select.value = value;
- }
- }
- }
-
- // 收起/展开参数配置表格
- function toggleParamTable(type) {
- const container = type === 'lora' ? document.getElementById('paramTableContainerLora') : document.getElementById('paramTableContainerFull');
- const btn = type === 'lora' ? document.getElementById('toggleParamBtnLora') : document.getElementById('toggleParamBtnFull');
- const icon = btn.querySelector('i');
- const text = type === 'lora' ? document.getElementById('toggleParamTextLora') : document.getElementById('toggleParamTextFull');
- if (container.classList.contains('hidden')) {
- container.classList.remove('hidden');
- icon.className = 'fa fa-chevron-up mr-1';
- text.textContent = '收起配置';
- } else {
- container.classList.add('hidden');
- icon.className = 'fa fa-chevron-down mr-1';
- text.textContent = '展开配置';
- }
- }
-
// ============ 自定义消息弹窗 ============
// 显示消息弹窗 - 使用 window 确保全局可访问
window.showMessage = function(title, message, type = 'info', onConfirm) {
diff --git a/web/pages/model-compare-create.html b/web/pages/model-compare-create.html
index 51fe07f..1b6150b 100644
--- a/web/pages/model-compare-create.html
+++ b/web/pages/model-compare-create.html
@@ -345,7 +345,9 @@
type: 'LLM',
source: 'trained',
model_path: model.path,
- train_method: method.name
+ train_method: method.name,
+ merged: model.merged, // 是否已合并
+ merging: model.merging // 是否正在合并
});
});
}
@@ -402,10 +404,15 @@
// 已训练模型区域
if (trainedModels.length > 0) {
+ // 统计已合并和未合并的数量
+ const mergedCount = trainedModels.filter(m => m.merged).length;
html += `
-
-
已训练模型 (${trainedModels.length})
+
+
+ 已训练模型 (${trainedModels.length})
+
+
${mergedCount > 0 ? `${mergedCount}个已合并可选择` : '需合并后才可选择'}
`;
@@ -420,7 +427,9 @@
// 渲染单个模型卡片
function renderModelCard(model) {
const isSelected = selectedModels.has(model.id);
- const isDisabled = !isSelected && selectedModels.size >= 4;
+ // 未合并的已训练模型不可选择
+ const isTrainedNotMerged = model.source === 'trained' && !model.merged;
+ const isDisabled = !isSelected && (selectedModels.size >= 4 || isTrainedNotMerged);
const typeText = {
'LLM': '大语言模型',
'CV': '计算机视觉',
@@ -430,19 +439,24 @@
}[model.type] || model.type || '其他';
// 处理字符串类型的ID
const modelId = typeof model.id === 'string' ? `'${model.id.replace(/'/g, "\\'")}'` : model.id;
+ // 未合并提示文字
+ const unmergedTip = isTrainedNotMerged ? '请先合并权重后再选择' : '';
return `
+ onclick="${isTrainedNotMerged ? `showMessage('提示', '该模型未合并权重,无法参与对比。请先在模型管理中合并权重。', 'warning')` : `toggleModel(${modelId})`}"
+ title="${unmergedTip}">
-
+
${isSelected ? '' : ''}
${model.name}
${typeText}
+ ${isTrainedNotMerged ? '未合并' : ''}
+ ${model.source === 'trained' && model.merged ? '已合并' : ''}
${model.description || '暂无描述'}
@@ -519,10 +533,15 @@
// 已训练模型区域
if (trainedModels.length > 0) {
+ // 统计已合并和未合并的数量
+ const mergedCount = trainedModels.filter(m => m.merged).length;
html += `
-
-
已训练模型 (${trainedModels.length})
+
+
+ 已训练模型 (${trainedModels.length})
+
+
${mergedCount > 0 ? `${mergedCount}个已合并可选择` : '需合并后才可选择'}
`;
diff --git a/web/pages/model-manage.html b/web/pages/model-manage.html
index d7b8d98..0580645 100644
--- a/web/pages/model-manage.html
+++ b/web/pages/model-manage.html
@@ -1,735 +1,735 @@
-
-
-
-
-
-
模型管理 / 远光软件微调平台
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
模型管理
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- | 模型名称 |
- 模型类型 |
- 用途 |
- 模型来源 |
- 描述 |
- 模型路径 |
- 创建时间 |
- 操作 |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
已训练模型存储在 /app/base/saves 目录下
-
-
-
-
-
- | 基座模型 |
- 训练方法 |
- 模型路径 |
- 操作 |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
模型管理 / 远光软件微调平台
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
模型管理
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 模型名称 |
+ 模型类型 |
+ 用途 |
+ 模型来源 |
+ 描述 |
+ 模型路径 |
+ 创建时间 |
+ 操作 |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
已训练模型存储在 /app/base/saves 目录下
+
+
+
+
+
+ | 基座模型 |
+ 训练方法 |
+ 模型路径 |
+ 操作 |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/pages/tools.html b/web/pages/tools.html
new file mode 100644
index 0000000..b706d2e
--- /dev/null
+++ b/web/pages/tools.html
@@ -0,0 +1,373 @@
+
+
+
+
+
+
其他工具 - 远光软件微调平台
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+