feat(base.html): 优化页面样式与结构,提升移动端兼容性

This commit is contained in:
songsenand 2026-04-09 08:01:55 +08:00
parent d5daba182a
commit e1efcc75a8
3 changed files with 429 additions and 479 deletions

View File

@ -1,256 +1,245 @@
<!DOCTYPE html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN" data-theme="light">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}AI模型训练监控看板{% endblock %}</title> <title>{% block title %}AI模型训练监控看板{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🧠</text></svg>" type="image/svg+xml"> <link
<link rel="alternate icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🧠</text></svg>" type="image/svg+xml"> rel="icon"
<link rel="stylesheet" href="{{ url_for('static', filename='css/bulma.css') }}"> href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🧠</text></svg>"
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> type="image/svg+xml"
<script src="{{ url_for('static', filename='js/plotly-2.27.0.min.js') }}" charset="utf-8"></script> />
<style> <link
@font-face { rel="alternate icon"
font-family: 'SourceHanSans'; href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🧠</text></svg>"
src: url("{{ url_for('static', filename='fonts/SourceHanSansSC-Medium.otf') }}") format('opentype'); type="image/svg+xml"
font-weight: normal; />
font-style: normal; <link
} rel="stylesheet"
href="{{ url_for('static', filename='css/bulma.css') }}"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<script
src="{{ url_for('static', filename='js/plotly-2.27.0.min.js') }}"
charset="utf-8"
></script>
<style>
@font-face {
font-family: "SourceHanSans";
src: url("{{ url_for('static', filename='fonts/SourceHanSansSC-Medium.otf') }}")
format("opentype");
font-weight: normal;
font-style: normal;
}
@font-face { @font-face {
font-family: 'SmileySans'; font-family: "SmileySans";
src: url("{{ url_for('static', filename='fonts/SmileySans-Oblique.ttf') }}") format('truetype'); src: url("{{ url_for('static', filename='fonts/SmileySans-Oblique.ttf') }}")
font-weight: normal; format("truetype");
font-style: italic; font-weight: normal;
} font-style: italic;
}
:root { :root {
--primary-color: #00d1b2; --primary-color: #00d1b2;
--secondary-color: #3273dc; --secondary-color: #3273dc;
--success-color: #23d160; --success-color: #23d160;
--warning-color: #ffdd57; --warning-color: #ffdd57;
--danger-color: #ff3860; --danger-color: #ff3860;
--dark-color: #363636; --dark-color: #363636;
--light-color: #f5f5f5; --light-color: #f5f5f5;
} }
body { body {
font-family: 'SourceHanSans', 'SmileySans', 'Segoe UI', 'Microsoft YaHei', sans-serif; font-family:
background-color: #f8f9fa; "SourceHanSans", "SmileySans", "Segoe UI",
min-height: 100vh; "Microsoft YaHei", sans-serif;
} min-height: 100vh;
}
.card { .metric-card {
border-radius: 12px; border-left: 4px solid var(--primary-color);
box-shadow: 0 4px 20px rgba(0,0,0,0.08); }
border: none;
transition: transform 0.3s ease, box-shadow 0.3s ease;
margin-bottom: 1.5rem;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 30px rgba(0,0,0,0.12);
}
.card-header {
background-color: transparent;
border-bottom: 1px solid #e8e8e8;
padding: 1.25rem 1.5rem;
}
.card-content {
padding: 1.5rem;
}
.metric-card {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-left: 4px solid var(--primary-color);
}
.metric-value {
font-size: 2.5rem;
font-weight: 800;
color: var(--dark-color);
line-height: 1;
margin-bottom: 0.25rem;
}
.metric-label {
font-size: 0.875rem;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.metric-delta {
font-size: 0.875rem;
font-weight: 600;
}
.positive-delta {
color: var(--success-color);
}
.negative-delta {
color: var(--danger-color);
}
.chart-container {
background: white;
border-radius: 12px;
padding: 1.5rem;
height: 100%;
}
.status-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
}
.status-active {
background-color: var(--success-color);
box-shadow: 0 0 10px var(--success-color);
}
.status-inactive {
background-color: var(--danger-color);
box-shadow: 0 0 10px var(--danger-color);
}
.loading {
opacity: 0.7;
pointer-events: none;
}
.footer {
background-color: var(--dark-color);
color: white;
padding: 2rem 1.5rem;
margin-top: 3rem;
}
@media (max-width: 768px) {
.metric-value { .metric-value {
font-size: 2rem; font-size: 2.5rem;
font-weight: 800;
color: var(--dark-color);
line-height: 1;
margin-bottom: 0.25rem;
} }
.card { .metric-label {
margin-bottom: 1rem; font-size: 0.875rem;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
} }
.chart-container { .metric-delta {
padding: 1rem; font-size: 0.875rem;
font-weight: 600;
} }
}
.control-panel { .positive-delta {
background: white; color: var(--success-color);
border-radius: 12px; }
padding: 1.5rem;
margin-bottom: 1.5rem;
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
<nav class="navbar is-primary" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="{{ url_for('index') }}">
<span class="icon is-large">
<i class="fas fa-chart-line fa-2x"></i>
</span>
<span class="title is-4 ml-2">AI模型训练监控看板</span>
</a>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarMenu"> .negative-delta {
<span aria-hidden="true"></span> color: var(--danger-color);
<span aria-hidden="true"></span> }
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarMenu" class="navbar-menu"> .status-indicator {
<div class="navbar-end"> display: inline-block;
<div class="navbar-item"> width: 10px;
<div class="buttons"> height: 10px;
<button class="button is-light"> border-radius: 50%;
<span class="icon"> margin-right: 8px;
<i class="fas fa-sync-alt"></i> }
</span>
<span>刷新数据</span> .status-active {
</button> background-color: var(--success-color);
<a href="{{ url_for('api_status') }}" class="button is-info" target="_blank"> box-shadow: 0 0 10px var(--success-color);
<span class="icon"> }
<i class="fas fa-code"></i>
</span> .status-inactive {
<span>API接口</span> background-color: var(--danger-color);
</a> box-shadow: 0 0 10px var(--danger-color);
}
.loading {
opacity: 0.7;
pointer-events: none;
}
@media (max-width: 768px) {
.metric-value {
font-size: 2rem;
}
}
.pagination .is-disabled,
.pagination a[disabled],
.pagination a.is-disabled {
cursor: not-allowed;
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
<nav
class="navbar is-primary"
role="navigation"
aria-label="main navigation"
>
<div class="navbar-brand">
<a class="navbar-item" href="{{ url_for('index') }}">
<span class="icon is-large">
<i class="fas fa-chart-line fa-2x"></i>
</span>
<span class="title is-4 ml-2">AI模型训练监控看板</span>
</a>
<a
role="button"
class="navbar-burger"
aria-label="menu"
aria-expanded="false"
data-target="navbarMenu"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarMenu" class="navbar-menu">
<div class="navbar-end">
<div class="navbar-item">
<div class="buttons">
<button class="button is-light" id="refreshBtn">
<span class="icon">
<i class="fas fa-sync-alt"></i>
</span>
<span>刷新数据</span>
</button>
<a
href="{{ url_for('api_status') }}"
class="button is-info"
target="_blank"
>
<span class="icon">
<i class="fas fa-code"></i>
</span>
<span>API接口</span>
</a>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </nav>
</nav>
<section class="section"> <section class="section">
<div class="container"> <div class="container">{% block content %}{% endblock %}</div>
{% block content %}{% endblock %} </section>
</div>
</section>
<footer class="footer"> <footer class="footer has-background-dark has-text-white mt-5">
<div class="content has-text-centered"> <div class="content has-text-centered">
<p> <p>
<strong>AI模型训练监控系统</strong> - 基于JSON旁路记录法的移动端友好监控方案 <strong>AI模型训练监控系统</strong> -
</p> 基于JSON旁路记录法的移动端友好监控方案
<p class="mt-2"> </p>
最后更新: <span id="lastUpdateTime">--:--:--</span> | <p class="mt-2">
数据源: <span id="dataSource">{{ data_source if data_source else '未设置' }}</span> | 最后更新: <span id="lastUpdateTime">--:--:--</span> |
刷新间隔: <span id="refreshInterval">5</span> 数据源:
</p> <span id="dataSource"
</div> >{{ data_source if data_source else '未设置' }}</span
</footer> >
| 刷新间隔: <span id="refreshInterval">5</span>
</p>
</div>
</footer>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener("DOMContentLoaded", function () {
const navbarBurger = document.querySelector('.navbar-burger'); const navbarBurger = document.querySelector(".navbar-burger");
const navbarMenu = document.querySelector('#navbarMenu'); const navbarMenu = document.querySelector("#navbarMenu");
if (navbarBurger) { if (navbarBurger) {
navbarBurger.addEventListener('click', () => { navbarBurger.addEventListener("click", () => {
navbarBurger.classList.toggle('is-active'); navbarBurger.classList.toggle("is-active");
navbarMenu.classList.toggle('is-active'); navbarMenu.classList.toggle("is-active");
}); });
} }
const refreshBtn = document.getElementById('refreshBtn'); const refreshBtn = document.getElementById("refreshBtn");
if (refreshBtn) { if (refreshBtn) {
refreshBtn.addEventListener('click', () => { refreshBtn.addEventListener("click", () => {
refreshBtn.classList.add('is-loading'); refreshBtn.classList.add("is-loading");
setTimeout(() => { setTimeout(() => {
refreshBtn.classList.remove('is-loading'); refreshBtn.classList.remove("is-loading");
}, 1000); }, 1000);
if (typeof window.refreshData === 'function') { if (typeof window.refreshData === "function") {
window.refreshData(); window.refreshData();
} }
}); });
} }
function updateTime() { function updateTime() {
const now = new Date(); const now = new Date();
const timeStr = now.toLocaleTimeString('zh-CN'); const timeStr = now.toLocaleTimeString("zh-CN");
document.getElementById('lastUpdateTime').textContent = timeStr; document.getElementById("lastUpdateTime").textContent =
} timeStr;
}
updateTime(); updateTime();
setInterval(updateTime, 1000); setInterval(updateTime, 1000);
}); });
</script> </script>
{% block extra_js %}{% endblock %} {% block extra_js %}{% endblock %}
</body> </body>
</html> </html>

View File

@ -1,16 +1,24 @@
{% extends "base.html" %} {% extends "base.html" %} {% block content %}
<div class="box mb-5">
{% block content %} <div class="columns is-desktop is-vcentered">
<div class="control-panel">
<div class="columns is-mobile is-vcentered">
<div class="column"> <div class="column">
<div class="field"> <div class="field">
<label class="label">数据源配置</label> <label class="label">数据源配置</label>
<div class="control"> <div class="control">
<div class="select is-fullwidth"> <div class="select is-fullwidth">
<select id="dataSourceSelect"> <select id="dataSourceSelect">
<option value="local" {% if data_source_type == 'local' %}selected{% endif %}>本地文件</option> <option
<option value="remote" {% if data_source_type == 'remote' %}selected{% endif %}>远程URL</option> value="local"
{% if data_source_type=="local" %} selected {% endif %}
>
本地文件
</option>
<option
value="remote"
{% if data_source_type=="remote" %} selected {% endif %}
>
远程URL
</option>
</select> </select>
</div> </div>
</div> </div>
@ -18,17 +26,37 @@
</div> </div>
<div class="column"> <div class="column">
<div class="field" id="localFileField" {% if data_source_type == 'remote' %}style="display: none;"{% endif %}> <div
class="field"
id="localFileField"
{% if data_source_type=="remote" %} style="display: none;"{% endif %}
>
<label class="label">本地文件路径</label> <label class="label">本地文件路径</label>
<div class="control"> <div class="control">
<input class="input" type="text" id="localFilePath" value="{{ data_source if data_source_type == 'local' else './output/training_status.json' }}" placeholder="./output/training_status.json"> <input
class="input"
type="text"
id="localFilePath"
value="{{ data_source if data_source_type == 'local' else './output/training_status.json' }}"
placeholder="./output/training_status.json"
/>
</div> </div>
</div> </div>
<div class="field" id="remoteUrlField" {% if data_source_type != 'remote' %}style="display: none;"{% endif %}> <div
class="field"
id="remoteUrlField"
{% if data_source_type !="remote" %} style="display: none;" {% endif %}
>
<label class="label">远程URL地址</label> <label class="label">远程URL地址</label>
<div class="control"> <div class="control">
<input class="input" type="text" id="remoteUrl" value="{{ data_source if data_source_type == 'remote' else '' }}" placeholder="http://服务器IP:端口/training_status.json"> <input
class="input"
type="text"
id="remoteUrl"
value="{{ data_source if data_source_type == 'remote' else '' }}"
placeholder="http://服务器IP:端口/training_status.json"
/>
</div> </div>
</div> </div>
</div> </div>
@ -39,22 +67,50 @@
<div class="control"> <div class="control">
<div class="select is-fullwidth"> <div class="select is-fullwidth">
<select id="refreshIntervalSelect"> <select id="refreshIntervalSelect">
<option value="1" {% if refresh_interval == 1 %}selected{% endif %}>1秒</option> <option
<option value="2" {% if refresh_interval == 2 %}selected{% endif %}>2秒</option> value="1"
<option value="5" {% if refresh_interval == 5 %}selected{% endif %}>5秒</option> {% if refresh_interval=="1" %} selected {% endif %}
<option value="10" {% if refresh_interval == 10 %}selected{% endif %}>10秒</option> >
<option value="30" {% if refresh_interval == 30 %}selected{% endif %}>30秒</option> 1秒
</option>
<option
value="2"
{%if refresh_interval=="2"%} selected {% endif %}
>
2秒
</option>
<option
value="5"
{% if refresh_interval=="5" %} selected {% endif %}
>
5秒
</option>
<option
value="10"
{%if refresh_interval=="10"%}selected{%endif%}
>
10秒
</option>
<option
value="30"
{% if refresh_interval=="30" %} selected {% endif %}
>
30秒
</option>
</select> </select>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="column is-narrow"> <div class="column is-12-mobile is-narrow-desktop">
<div class="field"> <div class="field">
<label class="label">&nbsp;</label> <label class="label">&nbsp;</label>
<div class="control"> <div class="control">
<button id="applyConfigBtn" class="button is-primary is-fullwidth"> <button
id="applyConfigBtn"
class="button is-primary is-fullwidth"
>
<span class="icon"> <span class="icon">
<i class="fas fa-check"></i> <i class="fas fa-check"></i>
</span> </span>
@ -89,7 +145,9 @@
<div class="column is-6-mobile is-3-tablet"> <div class="column is-6-mobile is-3-tablet">
<div class="has-text-centered"> <div class="has-text-centered">
<div class="metric-value" id="trainLoss">0.0000</div> <div class="metric-value" id="trainLoss">
0.0000
</div>
<div class="metric-label">训练损失</div> <div class="metric-label">训练损失</div>
<div class="metric-delta" id="trainLossDelta"></div> <div class="metric-delta" id="trainLossDelta"></div>
</div> </div>
@ -97,9 +155,14 @@
<div class="column is-6-mobile is-3-tablet"> <div class="column is-6-mobile is-3-tablet">
<div class="has-text-centered"> <div class="has-text-centered">
<div class="metric-value" id="trainAccuracy">0.0000</div> <div class="metric-value" id="trainAccuracy">
0.0000
</div>
<div class="metric-label">训练准确率</div> <div class="metric-label">训练准确率</div>
<div class="metric-delta" id="trainAccuracyDelta"></div> <div
class="metric-delta"
id="trainAccuracyDelta"
></div>
</div> </div>
</div> </div>
@ -113,17 +176,27 @@
<div class="column is-6-mobile is-3-tablet"> <div class="column is-6-mobile is-3-tablet">
<div class="has-text-centered"> <div class="has-text-centered">
<div class="metric-value" id="evalAccuracy">0.0000</div> <div class="metric-value" id="evalAccuracy">
0.0000
</div>
<div class="metric-label">评估准确率</div> <div class="metric-label">评估准确率</div>
<div class="metric-delta" id="evalAccuracyDelta"></div> <div
class="metric-delta"
id="evalAccuracyDelta"
></div>
</div> </div>
</div> </div>
<div class="column is-6-mobile is-3-tablet"> <div class="column is-6-mobile is-3-tablet">
<div class="has-text-centered"> <div class="has-text-centered">
<div class="metric-value" id="learningRate">0.00e+0</div> <div class="metric-value" id="learningRate">
0.00e+0
</div>
<div class="metric-label">学习率</div> <div class="metric-label">学习率</div>
<div class="metric-delta" id="learningRateDelta"></div> <div
class="metric-delta"
id="learningRateDelta"
></div>
</div> </div>
</div> </div>
@ -142,21 +215,21 @@
<div class="column is-12"> <div class="column is-12">
<div class="columns is-multiline"> <div class="columns is-multiline">
<div class="column is-12-tablet is-6-desktop"> <div class="column is-12-tablet is-6-desktop">
<div class="chart-container"> <div class="box">
<h3 class="title is-5 mb-4">损失曲线</h3> <h3 class="title is-5 mb-4">损失曲线</h3>
<div id="lossChart"></div> <div id="lossChart"></div>
</div> </div>
</div> </div>
<div class="column is-12-tablet is-6-desktop"> <div class="column is-12-tablet is-6-desktop">
<div class="chart-container"> <div class="box">
<h3 class="title is-5 mb-4">准确率曲线</h3> <h3 class="title is-5 mb-4">准确率曲线</h3>
<div id="accuracyChart"></div> <div id="accuracyChart"></div>
</div> </div>
</div> </div>
<div class="column is-12"> <div class="column is-12">
<div class="chart-container"> <div class="box">
<h3 class="title is-5 mb-4">学习率变化</h3> <h3 class="title is-5 mb-4">学习率变化</h3>
<div id="learningRateChart"></div> <div id="learningRateChart"></div>
</div> </div>
@ -166,11 +239,11 @@
<div class="column is-12"> <div class="column is-12">
<div class="card"> <div class="card">
<div class="card-header"> <header class="card-header">
<h3 class="title is-5">数据详情</h3> <p class="card-header-title">数据详情</p>
</div> </header>
<div class="card-content"> <div class="card-content">
<div class="table-container"> <div class="table-container" style="max-height: 500px; overflow-y: auto;">
<table class="table is-fullwidth is-striped is-hoverable"> <table class="table is-fullwidth is-striped is-hoverable">
<thead> <thead>
<tr> <tr>
@ -186,35 +259,19 @@
</thead> </thead>
<tbody id="dataTableBody"> <tbody id="dataTableBody">
<tr> <tr>
<td colspan="8" class="has-text-centered">加载数据中...</td> <td colspan="8" class="has-text-centered">
加载数据中...
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<nav class="pagination is-centered mt-4" role="navigation" aria-label="pagination">
<a class="pagination-previous" id="prevPageBtn" disabled>上一页</a>
<a class="pagination-next" id="nextPageBtn" disabled>下一页</a>
<ul class="pagination-list">
<li><span class="pagination-ellipsis">&hellip;</span></li>
<li><span id="pageInfo" class="pagination-link is-current">第1页</span></li>
<li><span class="pagination-ellipsis">&hellip;</span></li>
</ul>
<div class="field has-addons is-pulled-right">
<div class="control">
<input class="input" type="number" id="pageSizeInput" min="5" max="50" step="5" value="10" style="width: 80px;">
</div>
<div class="control">
<a class="button is-static">条/页</a>
</div>
</div>
</nav>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %} {% block extra_js %}
{% block extra_js %}
<script> <script>
let currentData = []; let currentData = [];
let refreshTimer = null; let refreshTimer = null;
@ -222,12 +279,6 @@
let currentDataSourceType = '{{ data_source_type }}'; let currentDataSourceType = '{{ data_source_type }}';
let currentDataSource = '{{ data_source }}'; let currentDataSource = '{{ data_source }}';
let lastDataHash = ''; let lastDataHash = '';
let currentPage = 1;
let pageSize = 10;
let totalPages = 1;
let prevPageBtn = null;
let nextPageBtn = null;
let pageInfo = null;
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const dataSourceSelect = document.getElementById('dataSourceSelect'); const dataSourceSelect = document.getElementById('dataSourceSelect');
@ -235,10 +286,7 @@
const remoteUrlField = document.getElementById('remoteUrlField'); const remoteUrlField = document.getElementById('remoteUrlField');
const refreshIntervalSelect = document.getElementById('refreshIntervalSelect'); const refreshIntervalSelect = document.getElementById('refreshIntervalSelect');
const applyConfigBtn = document.getElementById('applyConfigBtn'); const applyConfigBtn = document.getElementById('applyConfigBtn');
prevPageBtn = document.getElementById('prevPageBtn');
nextPageBtn = document.getElementById('nextPageBtn');
const pageSizeInput = document.getElementById('pageSizeInput');
pageInfo = document.getElementById('pageInfo');
dataSourceSelect.addEventListener('change', function() { dataSourceSelect.addEventListener('change', function() {
if (this.value === 'local') { if (this.value === 'local') {
@ -291,7 +339,6 @@
// 重置数据状态,因为数据源已改变 // 重置数据状态,因为数据源已改变
lastDataHash = ''; lastDataHash = '';
currentPage = 1;
clearInterval(refreshTimer); clearInterval(refreshTimer);
loadData(); loadData();
@ -327,41 +374,7 @@
} }
} }
// 分页事件监听器
if (prevPageBtn) {
prevPageBtn.addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
updateDataTable(currentData);
updatePagination();
}
});
}
if (nextPageBtn) {
nextPageBtn.addEventListener('click', () => {
if (currentPage < totalPages) {
currentPage++;
updateDataTable(currentData);
updatePagination();
}
});
}
if (pageSizeInput) {
pageSizeInput.addEventListener('change', () => {
const newPageSize = parseInt(pageSizeInput.value);
if (newPageSize >= 5 && newPageSize <= 50) {
pageSize = newPageSize;
currentPage = 1;
updateDataTable(currentData);
updatePagination();
} else {
pageSizeInput.value = pageSize;
showNotification('每页显示数量应在5到50之间', 'warning');
}
});
}
@ -411,7 +424,6 @@
} }
lastDataHash = newHash; lastDataHash = newHash;
currentPage = 1; // 新数据到来重置到第1页
currentData = data; currentData = data;
updateMetrics(data); updateMetrics(data);
updateCharts(data); updateCharts(data);
@ -614,27 +626,14 @@
if (!data || data.length === 0) { if (!data || data.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" class="has-text-centered">暂无数据</td></tr>'; tbody.innerHTML = '<tr><td colspan="8" class="has-text-centered">暂无数据</td></tr>';
updatePagination();
return; return;
} }
// 计算分页数据 // 反转数据顺序,最新的数据显示在表格顶部
const totalItems = data.length; const reversedData = data.slice().reverse();
totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
// 确保当前页在有效范围内
if (currentPage > totalPages) currentPage = totalPages;
if (currentPage < 1) currentPage = 1;
// 计算要显示的数据范围(从最新数据开始分页)
const startIndex = Math.max(0, totalItems - currentPage * pageSize);
const endIndex = Math.max(0, totalItems - (currentPage - 1) * pageSize);
// 获取数据并反转顺序(最新的数据在表格顶部)
const pageData = data.slice(startIndex, endIndex).reverse();
let html = ''; let html = '';
pageData.forEach(record => { reversedData.forEach(record => {
const timestamp = record.timestamp ? const timestamp = record.timestamp ?
new Date(record.timestamp).toLocaleString('zh-CN') : new Date(record.timestamp).toLocaleString('zh-CN') :
'N/A'; 'N/A';
@ -654,32 +653,9 @@
}); });
tbody.innerHTML = html; tbody.innerHTML = html;
updatePagination();
} }
function updatePagination() {
if (!prevPageBtn || !nextPageBtn || !pageInfo) return;
// 更新按钮状态
prevPageBtn.disabled = currentPage <= 1;
nextPageBtn.disabled = currentPage >= totalPages;
// 更新页数信息
pageInfo.textContent = `第${currentPage}页 / 共${totalPages}页`;
// 更新按钮样式
if (prevPageBtn.disabled) {
prevPageBtn.classList.add('is-disabled');
} else {
prevPageBtn.classList.remove('is-disabled');
}
if (nextPageBtn.disabled) {
nextPageBtn.classList.add('is-disabled');
} else {
nextPageBtn.classList.remove('is-disabled');
}
}
function showNotification(message, type = 'info') { function showNotification(message, type = 'info') {
const notification = document.createElement('div'); const notification = document.createElement('div');

View File

@ -1232,25 +1232,10 @@ def expand_and_train(
num_experts: int = typer.Option(20, "--num-experts", help="MoE专家数量"), num_experts: int = typer.Option(20, "--num-experts", help="MoE专家数量"),
max_seq_len: int = typer.Option(128, "--max-seq-len", help="最大序列长度"), max_seq_len: int = typer.Option(128, "--max-seq-len", help="最大序列长度"),
use_pinyin: bool = typer.Option(False, "--use-pinyin", help="是否使用拼音特征"), use_pinyin: bool = typer.Option(False, "--use-pinyin", help="是否使用拼音特征"),
# 两阶段训练参数
frozen_patience: int = typer.Option(
10,
"--frozen-patience",
help="冻结阶段验证损失连续不下降的epoch数触发切换到全量微调",
),
frozen_lr: float = typer.Option(1e-3, "--frozen-lr", help="冻结阶段学习率"),
full_lr: float = typer.Option(1e-4, "--full-lr", help="全量微调阶段学习率"),
frozen_scheduler: str = typer.Option(
"cosine", "--frozen-scheduler", help="冻结阶段学习率调度器类型cosine或plateau"
),
full_scheduler: str = typer.Option(
"cosine",
"--full-scheduler",
help="全量微调阶段学习率调度器类型cosine或plateau",
),
# 训练参数 # 训练参数
batch_size: int = typer.Option(128, "--batch-size", "-b", help="批次大小"), batch_size: int = typer.Option(128, "--batch-size", "-b", help="批次大小"),
num_epochs: int = typer.Option(10, "--num-epochs", help="训练轮数"), num_epochs: int = typer.Option(10, "--num-epochs", help="训练轮数"),
learning_rate: float = typer.Option(1e-5, "--learning-rate", "-lr", help="学习率"),
min_learning_rate: float = typer.Option( min_learning_rate: float = typer.Option(
1e-9, "--min-learning-rate", help="最小学习率" 1e-9, "--min-learning-rate", help="最小学习率"
), ),