1. 修改了一些bug
2. 做了一些调整,比如启动脚本,支持了tenmsorboard
This commit is contained in:
@@ -167,7 +167,9 @@ def start_training():
|
|||||||
train_method = data.get('train_method', 'lora')
|
train_method = data.get('train_method', 'lora')
|
||||||
|
|
||||||
# 创建输出目录(如果不存在)
|
# 创建输出目录(如果不存在)
|
||||||
output_model_name = data.get('output_model_name', f"{template}/{train_method}")
|
# 路径格式: /app/base/saves/{train_method}/{output_model_name}
|
||||||
|
output_model_name = data.get('output_model_name', template)
|
||||||
|
output_model_name = f"{train_method}/{output_model_name}"
|
||||||
if not output_model_name.startswith('/'):
|
if not output_model_name.startswith('/'):
|
||||||
output_model_name = f"/app/base/saves/{output_model_name}"
|
output_model_name = f"/app/base/saves/{output_model_name}"
|
||||||
output_dir = output_model_name
|
output_dir = output_model_name
|
||||||
@@ -278,7 +280,9 @@ def build_train_command(data, model_path, dataset_name=None):
|
|||||||
cmd.extend(['--finetuning_type', FINETUNING_TYPE_MAP.get(train_method, 'lora')])
|
cmd.extend(['--finetuning_type', FINETUNING_TYPE_MAP.get(train_method, 'lora')])
|
||||||
|
|
||||||
# 输出目录(确保是绝对路径)
|
# 输出目录(确保是绝对路径)
|
||||||
output_model_name = data.get('output_model_name', f"{template}/{train_method}")
|
# 路径格式: /app/base/saves/{train_method}/{output_model_name}
|
||||||
|
output_model_name = data.get('output_model_name', template)
|
||||||
|
output_model_name = f"{train_method}/{output_model_name}"
|
||||||
if not output_model_name.startswith('/'):
|
if not output_model_name.startswith('/'):
|
||||||
output_model_name = f"/app/base/saves/{output_model_name}"
|
output_model_name = f"/app/base/saves/{output_model_name}"
|
||||||
output_dir = output_model_name
|
output_dir = output_model_name
|
||||||
@@ -417,7 +421,12 @@ def delete_training_task(task_id):
|
|||||||
# 获取任务信息(用于删除日志文件)
|
# 获取任务信息(用于删除日志文件)
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("SELECT name, process_id FROM fine_tune WHERE id = %s", (task_id,))
|
# 尝试获取所有字段,如果tensorboard_log_dir不存在会报错
|
||||||
|
try:
|
||||||
|
cursor.execute("SELECT name, process_id, tensorboard_log_dir FROM fine_tune WHERE id = %s", (task_id,))
|
||||||
|
except:
|
||||||
|
# 如果列不存在,只获取基本字段
|
||||||
|
cursor.execute("SELECT name, process_id FROM fine_tune WHERE id = %s", (task_id,))
|
||||||
task_result = cursor.fetchone()
|
task_result = cursor.fetchone()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -425,6 +434,7 @@ def delete_training_task(task_id):
|
|||||||
return jsonify({'code': 1, 'message': '任务不存在'})
|
return jsonify({'code': 1, 'message': '任务不存在'})
|
||||||
|
|
||||||
task_name = task_result.get('name', 'unknown')
|
task_name = task_result.get('name', 'unknown')
|
||||||
|
tensorboard_log_dir = task_result.get('tensorboard_log_dir', '') if 'tensorboard_log_dir' in task_result else ''
|
||||||
|
|
||||||
# 删除日志文件 (logs/{日期}/{task_id}_{task_name}.log)
|
# 删除日志文件 (logs/{日期}/{task_id}_{task_name}.log)
|
||||||
try:
|
try:
|
||||||
@@ -444,6 +454,17 @@ def delete_training_task(task_id):
|
|||||||
except Exception as log_err:
|
except Exception as log_err:
|
||||||
logger.warning(f"删除日志文件失败: {log_err}")
|
logger.warning(f"删除日志文件失败: {log_err}")
|
||||||
|
|
||||||
|
# 删除 TensorBoard 进程(如果存在)
|
||||||
|
global tensorboard_process
|
||||||
|
if tensorboard_process and tensorboard_process.poll() is None:
|
||||||
|
try:
|
||||||
|
import signal
|
||||||
|
os.killpg(os.getpgid(tensorboard_process.pid), signal.SIGTERM)
|
||||||
|
tensorboard_process = None
|
||||||
|
logger.info(f"已停止 TensorBoard 进程")
|
||||||
|
except Exception as tb_err:
|
||||||
|
logger.warning(f"停止 TensorBoard 失败: {tb_err}")
|
||||||
|
|
||||||
# 删除数据库中的任务记录
|
# 删除数据库中的任务记录
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|||||||
@@ -204,44 +204,67 @@ def get_trained_models():
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 使用 /app/base/saves 目录(容器内路径)
|
# 多个可能的路径
|
||||||
saves_base_path = '/app/base/saves'
|
potential_paths = [
|
||||||
# 本地开发时的备用路径
|
'/app/base/saves', # 容器内路径
|
||||||
local_saves_path = os.path.join(PROJECT_ROOT, 'saves')
|
os.path.join(PROJECT_ROOT, 'saves'), # 本地开发路径
|
||||||
|
os.path.join(os.path.dirname(os.path.dirname(PROJECT_ROOT)), 'YG_FT_Base', 'saves'), # 上级目录
|
||||||
|
]
|
||||||
|
|
||||||
# 选择存在的路径
|
base_path = None
|
||||||
base_path = saves_base_path if os.path.exists(saves_base_path) else local_saves_path
|
for path in potential_paths:
|
||||||
|
logger.info(f"[DEBUG] 检查路径: {path}, exists: {os.path.exists(path)}")
|
||||||
|
if os.path.exists(path):
|
||||||
|
base_path = path
|
||||||
|
break
|
||||||
|
|
||||||
logger.info(f"[DEBUG] 已训练模型目录: {base_path}, exists: {os.path.exists(base_path)}")
|
logger.info(f"[DEBUG] 最终使用的路径: {base_path}")
|
||||||
|
|
||||||
models = []
|
models = []
|
||||||
if os.path.exists(base_path):
|
if base_path and os.path.exists(base_path):
|
||||||
for item in os.listdir(base_path):
|
logger.info(f"[DEBUG] 遍历目录: {base_path}")
|
||||||
item_path = os.path.join(base_path, item)
|
try:
|
||||||
if os.path.isdir(item_path):
|
# 路径结构: /app/base/saves/{train_method}/{model_name}/
|
||||||
# 检查是否是模板目录(包含训练方法的子目录)
|
# train_method: lora, full, qlora, dpo, cpt 等
|
||||||
sub_items = []
|
|
||||||
if os.path.exists(item_path):
|
|
||||||
for sub_item in os.listdir(item_path):
|
|
||||||
sub_path = os.path.join(item_path, sub_item)
|
|
||||||
if os.path.isdir(sub_path):
|
|
||||||
# 检查是否包含模型文件(adapter_model.bin 或 pytorch_model.bin 等)
|
|
||||||
has_model = False
|
|
||||||
for f in os.listdir(sub_path):
|
|
||||||
if f.endswith('.bin') or f.endswith('.safetensors'):
|
|
||||||
has_model = True
|
|
||||||
break
|
|
||||||
if has_model:
|
|
||||||
sub_items.append({
|
|
||||||
'name': sub_item,
|
|
||||||
'path': sub_path
|
|
||||||
})
|
|
||||||
|
|
||||||
models.append({
|
for train_method in os.listdir(base_path):
|
||||||
'name': item,
|
train_method_path = os.path.join(base_path, train_method)
|
||||||
'path': item_path,
|
if not os.path.isdir(train_method_path):
|
||||||
'train_methods': sub_items
|
continue
|
||||||
})
|
|
||||||
|
logger.info(f"[DEBUG] 检查训练方法目录: {train_method}")
|
||||||
|
model_count = 0
|
||||||
|
|
||||||
|
# 遍历模型文件夹
|
||||||
|
for model_name in os.listdir(train_method_path):
|
||||||
|
model_path = os.path.join(train_method_path, model_name)
|
||||||
|
if not os.path.isdir(model_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查是否有模型文件
|
||||||
|
try:
|
||||||
|
files = os.listdir(model_path)
|
||||||
|
logger.info(f"[DEBUG] {train_method}/{model_name} 文件: {files[:5]}...")
|
||||||
|
has_model = any(f.endswith('.bin') or f.endswith('.safetensors') for f in files)
|
||||||
|
|
||||||
|
if has_model:
|
||||||
|
logger.info(f"[DEBUG] 找到模型: {train_method}/{model_name}")
|
||||||
|
models.append({
|
||||||
|
'name': model_name,
|
||||||
|
'path': model_path,
|
||||||
|
'train_methods': [{
|
||||||
|
'name': train_method,
|
||||||
|
'path': model_path
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
model_count += 1
|
||||||
|
except Exception as file_err:
|
||||||
|
logger.error(f"[DEBUG] 读取 {model_path} 失败: {file_err}")
|
||||||
|
|
||||||
|
logger.info(f"[DEBUG] {train_method} 找到 {model_count} 个模型")
|
||||||
|
|
||||||
|
except Exception as list_err:
|
||||||
|
logger.error(f"[DEBUG] 遍历目录失败: {list_err}")
|
||||||
|
|
||||||
logger.info(f"[DEBUG] 找到 {len(models)} 个已训练模型")
|
logger.info(f"[DEBUG] 找到 {len(models)} 个已训练模型")
|
||||||
|
|
||||||
@@ -249,7 +272,7 @@ def get_trained_models():
|
|||||||
'code': 0,
|
'code': 0,
|
||||||
'data': {
|
'data': {
|
||||||
'models': models,
|
'models': models,
|
||||||
'base_path': base_path
|
'base_path': base_path or ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ def init_database():
|
|||||||
valid_ratio INT DEFAULT 10,
|
valid_ratio INT DEFAULT 10,
|
||||||
output_model_name VARCHAR(255),
|
output_model_name VARCHAR(255),
|
||||||
process_id INT COMMENT '训练进程ID',
|
process_id INT COMMENT '训练进程ID',
|
||||||
|
tensorboard_log_dir VARCHAR(255) COMMENT 'TensorBoard日志目录',
|
||||||
status VARCHAR(50) DEFAULT 'pending',
|
status VARCHAR(50) DEFAULT 'pending',
|
||||||
progress INT DEFAULT 0,
|
progress INT DEFAULT 0,
|
||||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|||||||
49
start_all.sh
49
start_all.sh
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# YG_FT_Base 统一启动脚本
|
# YG_FT_Base 统一启动脚本
|
||||||
# 同时启动后端 API 服务和 Web 静态服务器
|
# 同时启动后端 API 服务、Web 静态服务器和 TensorBoard
|
||||||
# 使用方法: bash start_all.sh
|
# 使用方法: bash start_all.sh
|
||||||
|
|
||||||
# 自动修复脚本换行符
|
# 自动修复脚本换行符
|
||||||
@@ -34,6 +34,7 @@ fi
|
|||||||
echo "📦 端口配置:"
|
echo "📦 端口配置:"
|
||||||
echo " - 后端 API: $API_PORT"
|
echo " - 后端 API: $API_PORT"
|
||||||
echo " - Web 服务器: $WEB_PORT"
|
echo " - Web 服务器: $WEB_PORT"
|
||||||
|
echo " - TensorBoard: 6006"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 检查端口是否已被占用
|
# 检查端口是否已被占用
|
||||||
@@ -70,6 +71,36 @@ start_api() {
|
|||||||
echo "$API_PID" > /tmp/ygft_api.pid
|
echo "$API_PID" > /tmp/ygft_api.pid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 启动 TensorBoard 服务
|
||||||
|
start_tensorboard() {
|
||||||
|
echo ""
|
||||||
|
echo "🚀 启动 TensorBoard 服务..."
|
||||||
|
|
||||||
|
# 检查端口
|
||||||
|
if ! check_port 6006; then
|
||||||
|
echo "⚠️ 端口 6006 已被占用,TensorBoard 可能已在运行"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 确保日志目录存在
|
||||||
|
LOG_DIR="/app/base/saves"
|
||||||
|
if [ ! -d "$LOG_DIR" ]; then
|
||||||
|
LOG_DIR="$SCRIPT_DIR/saves"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$LOG_DIR" ]; then
|
||||||
|
echo "⚠️ 日志目录不存在,跳过 TensorBoard 启动"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 启动 TensorBoard(后台运行)
|
||||||
|
nohup tensorboard --logdir "$LOG_DIR" --port 6006 --bind_all > "$LOG_DIR/tensorboard.log" 2>&1 &
|
||||||
|
TB_PID=$!
|
||||||
|
echo "✅ TensorBoard 服务已启动 (PID: $TB_PID, 端口: 6006)"
|
||||||
|
echo "$TB_PID" > /tmp/ygft_tensorboard.pid
|
||||||
|
echo "📊 TensorBoard 访问地址: http://localhost:6006"
|
||||||
|
}
|
||||||
|
|
||||||
# 启动 Web 静态服务器
|
# 启动 Web 静态服务器
|
||||||
start_web() {
|
start_web() {
|
||||||
echo ""
|
echo ""
|
||||||
@@ -105,9 +136,16 @@ stop_all() {
|
|||||||
echo "✅ Web 服务已停止"
|
echo "✅ Web 服务已停止"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f /tmp/ygft_tensorboard.pid ]; then
|
||||||
|
kill $(cat /tmp/ygft_tensorboard.pid) 2>/dev/null
|
||||||
|
rm /tmp/ygft_tensorboard.pid
|
||||||
|
echo "✅ TensorBoard 服务已停止"
|
||||||
|
fi
|
||||||
|
|
||||||
# 清理可能残留的进程
|
# 清理可能残留的进程
|
||||||
pkill -f "src/main.py" 2>/dev/null
|
pkill -f "src/main.py" 2>/dev/null
|
||||||
pkill -f "http.server $WEB_PORT" 2>/dev/null
|
pkill -f "http.server $WEB_PORT" 2>/dev/null
|
||||||
|
pkill -f "tensorboard.*6006" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# 显示状态
|
# 显示状态
|
||||||
@@ -128,16 +166,24 @@ status() {
|
|||||||
echo "❌ Web 服务: 未运行"
|
echo "❌ Web 服务: 未运行"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f /tmp/ygft_tensorboard.pid ] && kill -0 $(cat /tmp/ygft_tensorboard.pid) 2>/dev/null; then
|
||||||
|
echo "✅ TensorBoard: 运行中 (PID: $(cat /tmp/ygft_tensorboard.pid), 端口: 6006)"
|
||||||
|
else
|
||||||
|
echo "❌ TensorBoard: 未运行"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "🌐 访问地址:"
|
echo "🌐 访问地址:"
|
||||||
echo " - 后端 API: http://localhost:$API_PORT"
|
echo " - 后端 API: http://localhost:$API_PORT"
|
||||||
echo " - Web 页面: http://localhost:$WEB_PORT/pages/main.html"
|
echo " - Web 页面: http://localhost:$WEB_PORT/pages/main.html"
|
||||||
|
echo " - TensorBoard: http://localhost:6006"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 主菜单
|
# 主菜单
|
||||||
case "$1" in
|
case "$1" in
|
||||||
start)
|
start)
|
||||||
start_api
|
start_api
|
||||||
|
start_tensorboard
|
||||||
start_web
|
start_web
|
||||||
echo ""
|
echo ""
|
||||||
echo "===================================="
|
echo "===================================="
|
||||||
@@ -153,6 +199,7 @@ case "$1" in
|
|||||||
stop_all
|
stop_all
|
||||||
sleep 1
|
sleep 1
|
||||||
start_api
|
start_api
|
||||||
|
start_tensorboard
|
||||||
start_web
|
start_web
|
||||||
echo ""
|
echo ""
|
||||||
echo "===================================="
|
echo "===================================="
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a href="main.html?page=my-models" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
<a href="model-manage.html" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
||||||
<i class="fa fa-database w-5 text-center"></i>
|
<i class="fa fa-database w-5 text-center"></i>
|
||||||
<span class="ml-2">我的模型</span>
|
<span class="ml-2">我的模型</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -5,6 +5,18 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>添加自定义工具 / 远光软件微调平台</title>
|
<title>添加自定义工具 / 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
.sidebar-section-title {
|
.sidebar-section-title {
|
||||||
@@ -105,7 +117,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a href="main.html?page=my-models" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
<a href="model-manage.html" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
||||||
<i class="fa fa-database w-5 text-center"></i>
|
<i class="fa fa-database w-5 text-center"></i>
|
||||||
<span class="ml-2">我的模型</span>
|
<span class="ml-2">我的模型</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -259,13 +271,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
// 使用 IIFE 避免全局变量污染
|
||||||
const getApiBase = () => {
|
(function() {
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
// 当前是否为编辑模式
|
// 当前是否为编辑模式
|
||||||
let isEditMode = false;
|
let isEditMode = false;
|
||||||
@@ -500,6 +514,7 @@
|
|||||||
if (onConfirm) onConfirm();
|
if (onConfirm) onConfirm();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- 自定义消息弹窗 -->
|
<!-- 自定义消息弹窗 -->
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>上传数据集 - 远光软件微调平台</title>
|
<title>上传数据集 - 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
/* 侧边栏滑块动画 */
|
/* 侧边栏滑块动画 */
|
||||||
@@ -109,7 +120,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a href="main.html?page=my-models" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
<a href="model-manage.html" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
||||||
<i class="fa fa-database w-5 text-center"></i>
|
<i class="fa fa-database w-5 text-center"></i>
|
||||||
<span class="ml-2">我的模型</span>
|
<span class="ml-2">我的模型</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -393,13 +404,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
// 使用 IIFE 避免全局变量污染
|
||||||
const getApiBase = () => {
|
(function() {
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
// 返回页面
|
// 返回页面
|
||||||
let backUrl = 'main.html?page=dataset-manage';
|
let backUrl = 'main.html?page=dataset-manage';
|
||||||
@@ -1129,6 +1142,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- 预览弹窗 -->
|
<!-- 预览弹窗 -->
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>数据管理-文件预览 - 远光软件微调平台</title>
|
<title>数据管理-文件预览 - 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
.sidebar-section-title {
|
.sidebar-section-title {
|
||||||
@@ -212,13 +223,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
// 使用 IIFE 避免全局变量污染
|
||||||
const getApiBase = () => {
|
(function() {
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
// 获取URL参数
|
// 获取URL参数
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
@@ -405,6 +418,7 @@
|
|||||||
alert('删除失败: ' + error.message);
|
alert('删除失败: ' + error.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>创建训练任务 / 远光软件微调平台</title>
|
<title>创建训练任务 / 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
.sidebar-section-title {
|
.sidebar-section-title {
|
||||||
@@ -111,7 +122,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a href="main.html?page=my-models" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
<a href="model-manage.html" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
||||||
<i class="fa fa-database w-5 text-center"></i>
|
<i class="fa fa-database w-5 text-center"></i>
|
||||||
<span class="ml-2">我的模型</span>
|
<span class="ml-2">我的模型</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -662,13 +673,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
// 使用 IIFE 避免全局变量污染
|
||||||
const getApiBase = () => {
|
(function() {
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
// 页面加载完成后初始化
|
// 页面加载完成后初始化
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
@@ -1213,9 +1226,9 @@
|
|||||||
const trainMethod = formData.get('train_method') || 'lora';
|
const trainMethod = formData.get('train_method') || 'lora';
|
||||||
const methodMap = { 'lora': 'lora', 'full': 'full' };
|
const methodMap = { 'lora': 'lora', 'full': 'full' };
|
||||||
|
|
||||||
// 获取输出模型名称(使用任务名称)
|
// 获取输出模型名称(使用任务名称,路径格式: {train_method}/{taskName})
|
||||||
const taskName = formData.get('name') || 'task_name';
|
const taskName = formData.get('name') || 'task_name';
|
||||||
const outputModelName = taskName;
|
const outputModelName = `${trainMethod}/${taskName}`;
|
||||||
const outputDir = outputModelName.startsWith('/') ? outputModelName : `/app/base/saves/${outputModelName}`;
|
const outputDir = outputModelName.startsWith('/') ? outputModelName : `/app/base/saves/${outputModelName}`;
|
||||||
|
|
||||||
// 获取数据集名称
|
// 获取数据集名称
|
||||||
@@ -1313,6 +1326,12 @@
|
|||||||
// 初始化时更新一次
|
// 初始化时更新一次
|
||||||
setTimeout(updateCommandPreview, 500);
|
setTimeout(updateCommandPreview, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将函数暴露到全局作用域,供 onclick 调用
|
||||||
|
window.toggleGPUSelection = toggleGPUSelection;
|
||||||
|
window.submitForm = submitForm;
|
||||||
|
window.toggleTrainMethod = toggleTrainMethod;
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- 自定义消息弹窗 -->
|
<!-- 自定义消息弹窗 -->
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>远光软件微调平台 - 登录</title>
|
<title>远光软件微调平台 - 登录</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
.login-card-shadow {
|
.login-card-shadow {
|
||||||
@@ -115,13 +126,15 @@
|
|||||||
|
|
||||||
<!-- 简单的交互脚本 -->
|
<!-- 简单的交互脚本 -->
|
||||||
<script>
|
<script>
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
// 使用 IIFE 避免全局变量污染
|
||||||
const getApiBase = () => {
|
(function() {
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
// 密码显示/隐藏切换
|
// 密码显示/隐藏切换
|
||||||
const togglePassword = document.getElementById('togglePassword');
|
const togglePassword = document.getElementById('togglePassword');
|
||||||
@@ -172,6 +185,7 @@
|
|||||||
errorMsg.classList.remove('hidden');
|
errorMsg.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -5,6 +5,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>模型调优 - 远光软件微调平台</title>
|
<title>模型调优 - 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<script src="../lib/marked.min.js"></script>
|
<script src="../lib/marked.min.js"></script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
@@ -189,7 +200,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a href="#" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
<a href="model-manage.html" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
||||||
<i class="fa fa-database w-5 text-center"></i>
|
<i class="fa fa-database w-5 text-center"></i>
|
||||||
<span class="ml-2">我的模型</span>
|
<span class="ml-2">我的模型</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -310,12 +321,17 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
||||||
const getApiBase = () => {
|
if (typeof window.getApiBase !== 'function') {
|
||||||
const protocol = window.location.protocol;
|
window.getApiBase = () => {
|
||||||
const hostname = window.location.hostname;
|
const protocol = window.location.protocol;
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const hostname = window.location.hostname;
|
||||||
};
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
const API_BASE = getApiBase();
|
};
|
||||||
|
}
|
||||||
|
if (typeof window.API_BASE === 'undefined') {
|
||||||
|
window.API_BASE = window.getApiBase();
|
||||||
|
}
|
||||||
|
const API_BASE = window.API_BASE;
|
||||||
|
|
||||||
// 日志自动刷新相关变量
|
// 日志自动刷新相关变量
|
||||||
let logRefreshTimer = null;
|
let logRefreshTimer = null;
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>模型对比 - 远光软件微调平台</title>
|
<title>模型对比 - 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
.sidebar-section-title {
|
.sidebar-section-title {
|
||||||
@@ -372,14 +383,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
console.log('>>> model-compare-chat.html 脚本开始加载');
|
// 使用 IIFE 避免全局变量污染
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
(function() {
|
||||||
const getApiBase = () => {
|
console.log('>>> model-compare-chat.html 脚本开始加载');
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
// 模拟模型数据(用于演示)
|
// 模拟模型数据(用于演示)
|
||||||
const mockModels = [
|
const mockModels = [
|
||||||
@@ -605,6 +618,7 @@
|
|||||||
function delay(ms) {
|
function delay(ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>新建对比 - 远光软件微调平台</title>
|
<title>新建对比 - 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
.sidebar-section-title {
|
.sidebar-section-title {
|
||||||
@@ -284,13 +295,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
// 使用 IIFE 避免全局变量污染
|
||||||
const getApiBase = () => {
|
(function() {
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
let allModels = [];
|
let allModels = [];
|
||||||
let selectedModels = new Set();
|
let selectedModels = new Set();
|
||||||
@@ -605,6 +618,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>模型对比结果 - 远光软件微调平台</title>
|
<title>模型对比结果 - 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
.sidebar-section-title {
|
.sidebar-section-title {
|
||||||
@@ -232,14 +243,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
console.log('>>> model-compare-result.html 脚本开始加载');
|
// 使用 IIFE 避免全局变量污染
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
(function() {
|
||||||
const getApiBase = () => {
|
console.log('>>> model-compare-result.html 脚本开始加载');
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
// 模拟模型数据(用于演示)
|
// 模拟模型数据(用于演示)
|
||||||
const mockModels = [
|
const mockModels = [
|
||||||
@@ -590,6 +603,7 @@
|
|||||||
if (restartBtn) {
|
if (restartBtn) {
|
||||||
restartBtn.onclick = restartQuestion;
|
restartBtn.onclick = restartQuestion;
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>添加评测维度 / 远光软件微调平台</title>
|
<title>添加评测维度 / 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
/* 侧边栏滑块动画 */
|
/* 侧边栏滑块动画 */
|
||||||
@@ -205,7 +216,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a href="main.html?page=my-models" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
<a href="model-manage.html" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
||||||
<i class="fa fa-database w-5 text-center"></i>
|
<i class="fa fa-database w-5 text-center"></i>
|
||||||
<span class="ml-2">我的模型</span>
|
<span class="ml-2">我的模型</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -456,13 +467,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
// 使用 IIFE 避免全局变量污染
|
||||||
const getApiBase = () => {
|
(function() {
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
// 加载评测模型列表(用途为 evaluation 的模型)
|
// 加载评测模型列表(用途为 evaluation 的模型)
|
||||||
async function loadEvalModels() {
|
async function loadEvalModels() {
|
||||||
@@ -1304,7 +1317,7 @@
|
|||||||
}
|
}
|
||||||
this._onConfirm = null;
|
this._onConfirm = null;
|
||||||
};
|
};
|
||||||
}
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- 自定义消息弹窗 -->
|
<!-- 自定义消息弹窗 -->
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>新建评测 / 远光软件微调平台</title>
|
<title>新建评测 / 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
/* 侧边栏滑块动画 */
|
/* 侧边栏滑块动画 */
|
||||||
@@ -115,7 +126,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a href="main.html?page=my-models" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
<a href="model-manage.html" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
||||||
<i class="fa fa-database w-5 text-center"></i>
|
<i class="fa fa-database w-5 text-center"></i>
|
||||||
<span class="ml-2">我的模型</span>
|
<span class="ml-2">我的模型</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -357,13 +368,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
// 使用 IIFE 避免全局变量污染
|
||||||
const getApiBase = () => {
|
(function() {
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
// 页面加载完成后初始化
|
// 页面加载完成后初始化
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
@@ -588,6 +601,7 @@
|
|||||||
showMessage('错误', '创建失败: ' + error.message, 'error');
|
showMessage('错误', '创建失败: ' + error.message, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- 自定义消息弹窗 -->
|
<!-- 自定义消息弹窗 -->
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>模型评测 / 远光软件微调平台</title>
|
<title>模型评测 / 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
.bg-primary { background-color: #1890ff; }
|
.bg-primary { background-color: #1890ff; }
|
||||||
@@ -112,13 +123,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
// 使用 IIFE 避免全局变量污染
|
||||||
const getApiBase = () => {
|
(function() {
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
// Tab 内容切换函数(供 main.html 调用)
|
// Tab 内容切换函数(供 main.html 调用)
|
||||||
window.switchTabContent = function(tabId) {
|
window.switchTabContent = function(tabId) {
|
||||||
@@ -452,6 +465,7 @@
|
|||||||
} else {
|
} else {
|
||||||
initPage();
|
initPage();
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>添加模型 / 远光软件微调平台</title>
|
<title>添加模型 / 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
.sidebar-section-title {
|
.sidebar-section-title {
|
||||||
@@ -103,7 +114,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a href="main.html?page=my-models" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
<a href="model-manage.html" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
||||||
<i class="fa fa-database w-5 text-center"></i>
|
<i class="fa fa-database w-5 text-center"></i>
|
||||||
<span class="ml-2">我的模型</span>
|
<span class="ml-2">我的模型</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -331,13 +342,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
// 使用 IIFE 避免全局变量污染
|
||||||
const getApiBase = () => {
|
(function() {
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
// 返回页面
|
// 返回页面
|
||||||
let backUrl = 'main.html?page=model-manage';
|
let backUrl = 'main.html?page=model-manage';
|
||||||
@@ -662,6 +675,7 @@
|
|||||||
function goBack() {
|
function goBack() {
|
||||||
window.location.href = backUrl;
|
window.location.href = backUrl;
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- 自定义消息弹窗 -->
|
<!-- 自定义消息弹窗 -->
|
||||||
|
|||||||
@@ -5,6 +5,18 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>模型管理 / 远光软件微调平台</title>
|
<title>模型管理 / 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
// 禁用 Tailwind 开发模式警告
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
.sidebar-section-title {
|
.sidebar-section-title {
|
||||||
@@ -95,7 +107,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item-wrapper">
|
<div class="nav-item-wrapper">
|
||||||
<a href="#" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
<a href="model-manage.html" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
|
||||||
<i class="fa fa-database w-5 text-center"></i>
|
<i class="fa fa-database w-5 text-center"></i>
|
||||||
<span class="ml-2">我的模型</span>
|
<span class="ml-2">我的模型</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -248,6 +260,7 @@
|
|||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">用途</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">用途</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">模型来源</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">模型来源</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">描述</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">描述</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">模型路径</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">创建时间</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">创建时间</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -290,16 +303,18 @@
|
|||||||
<p class="text-gray-500">暂无已训练模型</p>
|
<p class="text-gray-500">暂无已训练模型</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
|
// 使用 IIFE 避免全局变量污染
|
||||||
const getApiBase = () => {
|
(function() {
|
||||||
const protocol = window.location.protocol;
|
// API 基础地址 - 优先使用 main.html 中定义的全局变量
|
||||||
const hostname = window.location.hostname;
|
const getApiBase = () => {
|
||||||
return `${protocol}//${hostname}:7861/api`;
|
const protocol = window.location.protocol;
|
||||||
};
|
const hostname = window.location.hostname;
|
||||||
const API_BASE = getApiBase();
|
return `${protocol}//${hostname}:7861/api`;
|
||||||
|
};
|
||||||
|
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
|
||||||
|
|
||||||
let allModels = [];
|
let allModels = [];
|
||||||
let trainedModels = [];
|
let trainedModels = [];
|
||||||
@@ -339,19 +354,94 @@
|
|||||||
// 加载模型数据
|
// 加载模型数据
|
||||||
async function loadModels() {
|
async function loadModels() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/model-manage`);
|
// 并行加载数据库模型和已训练模型
|
||||||
const result = await response.json();
|
const [dbResponse, trainedResponse] = await Promise.all([
|
||||||
|
fetch(`${API_BASE}/model-manage`),
|
||||||
|
fetch(`${API_BASE}/model-manage/trained-models`)
|
||||||
|
]);
|
||||||
|
|
||||||
if (result.code === 0) {
|
const dbResult = await dbResponse.json();
|
||||||
allModels = result.data || [];
|
const trainedResult = await trainedResponse.json();
|
||||||
renderModels();
|
|
||||||
|
console.log('[DEBUG] 数据库模型数量:', dbResult.data?.length || 0);
|
||||||
|
console.log('[DEBUG] 已训练模型API响应:', trainedResult);
|
||||||
|
|
||||||
|
// 数据库模型
|
||||||
|
let dbModels = [];
|
||||||
|
if (dbResult.code === 0) {
|
||||||
|
dbModels = dbResult.data || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 已训练模型 - 转换为统一格式
|
||||||
|
trainedModels = [];
|
||||||
|
if (trainedResult.code === 0) {
|
||||||
|
const trainedData = trainedResult.data?.models || [];
|
||||||
|
console.log('[DEBUG] 已训练模型数据:', trainedData);
|
||||||
|
trainedData.forEach(model => {
|
||||||
|
// 每个训练方法作为一个模型条目
|
||||||
|
if (model.train_methods && model.train_methods.length > 0) {
|
||||||
|
model.train_methods.forEach(method => {
|
||||||
|
trainedModels.push({
|
||||||
|
id: `trained_${model.name}_${method.name}`.replace(/[^a-zA-Z0-9]/g, '_'),
|
||||||
|
name: `${model.name} (${method.name})`,
|
||||||
|
type: 'LLM',
|
||||||
|
purpose: 'inference',
|
||||||
|
model_source: 'local',
|
||||||
|
path: method.path,
|
||||||
|
description: `基于 ${model.name} 的${getMethodDisplayName(method.name)}训练模型`,
|
||||||
|
create_time: new Date().toISOString(),
|
||||||
|
isTrained: true,
|
||||||
|
baseModel: model.name,
|
||||||
|
trainMethod: method.name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 没有训练方法的也添加为模型
|
||||||
|
trainedModels.push({
|
||||||
|
id: `trained_${model.name}`.replace(/[^a-zA-Z0-9]/g, '_'),
|
||||||
|
name: model.name,
|
||||||
|
type: 'LLM',
|
||||||
|
purpose: 'inference',
|
||||||
|
model_source: 'local',
|
||||||
|
path: model.path,
|
||||||
|
description: '已训练模型',
|
||||||
|
create_time: new Date().toISOString(),
|
||||||
|
isTrained: true,
|
||||||
|
baseModel: model.name,
|
||||||
|
trainMethod: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并所有模型
|
||||||
|
allModels = [...dbModels, ...trainedModels];
|
||||||
|
console.log('[DEBUG] 数据库模型:', dbModels.length);
|
||||||
|
console.log('[DEBUG] 已训练模型:', trainedModels.length);
|
||||||
|
console.log('[DEBUG] 合并后的模型总数:', allModels.length);
|
||||||
|
renderModels();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载模型失败:', error);
|
console.error('加载模型失败:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载已训练模型数据
|
// 获取训练方法显示名称
|
||||||
|
function getMethodDisplayName(method) {
|
||||||
|
const methodMap = {
|
||||||
|
'lora': 'LoRA',
|
||||||
|
'qlora': 'QLoRA',
|
||||||
|
'full': '全量微调',
|
||||||
|
'prefix': 'Prefix Tuning',
|
||||||
|
'adapter': 'Adapter',
|
||||||
|
'lora_plus': 'LoRA+',
|
||||||
|
'peft': 'PEFT',
|
||||||
|
'adalora': 'AdaLoRA',
|
||||||
|
'longlora': 'LongLoRA'
|
||||||
|
};
|
||||||
|
return methodMap[method] || method;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载已训练模型数据(仅用于Trained Tab)
|
||||||
async function loadTrainedModels() {
|
async function loadTrainedModels() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/model-manage/trained-models`);
|
const response = await fetch(`${API_BASE}/model-manage/trained-models`);
|
||||||
@@ -375,7 +465,8 @@
|
|||||||
|
|
||||||
// 渲染模型列表
|
// 渲染模型列表
|
||||||
function renderModels() {
|
function renderModels() {
|
||||||
const searchValue = document.getElementById('searchInput').value.toLowerCase();
|
const searchInput = document.getElementById('searchInput');
|
||||||
|
const searchValue = searchInput ? searchInput.value.toLowerCase() : '';
|
||||||
let filteredModels = allModels;
|
let filteredModels = allModels;
|
||||||
|
|
||||||
// 按 Tab 筛选
|
// 按 Tab 筛选
|
||||||
@@ -394,13 +485,15 @@
|
|||||||
const tbody = document.getElementById('modelsBody');
|
const tbody = document.getElementById('modelsBody');
|
||||||
const emptyState = document.getElementById('emptyState');
|
const emptyState = document.getElementById('emptyState');
|
||||||
|
|
||||||
|
if (!tbody) return;
|
||||||
|
|
||||||
if (filteredModels.length === 0) {
|
if (filteredModels.length === 0) {
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
emptyState.classList.remove('hidden');
|
if (emptyState) emptyState.classList.remove('hidden');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emptyState.classList.add('hidden');
|
if (emptyState) emptyState.classList.add('hidden');
|
||||||
|
|
||||||
tbody.innerHTML = filteredModels.map(item => {
|
tbody.innerHTML = filteredModels.map(item => {
|
||||||
// 模型类型
|
// 模型类型
|
||||||
@@ -429,10 +522,38 @@
|
|||||||
};
|
};
|
||||||
const sourceDisplay = sourceMap[item.model_source] || item.model_source || '-';
|
const sourceDisplay = sourceMap[item.model_source] || item.model_source || '-';
|
||||||
|
|
||||||
|
// 判断是否是已训练模型
|
||||||
|
const isTrained = item.isTrained === true;
|
||||||
|
const trainedBadge = isTrained ? '<span class="ml-2 px-2 py-0.5 text-xs font-medium rounded bg-green-100 text-green-700">已训练</span>' : '';
|
||||||
|
|
||||||
|
// 操作按钮
|
||||||
|
let actionButtons = '';
|
||||||
|
if (isTrained) {
|
||||||
|
// 已训练模型:显示查看和复制路径按钮
|
||||||
|
actionButtons = `
|
||||||
|
<button onclick="viewTrainedModel('${(item.path || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'")}')" class="text-primary hover:text-primary/80 mr-3">
|
||||||
|
<i class="fa fa-folder-open"></i> 查看
|
||||||
|
</button>
|
||||||
|
<button onclick="copyModelPath('${(item.path || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'")}')" class="text-gray-500 hover:text-gray-700">
|
||||||
|
<i class="fa fa-copy"></i> 复制路径
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
// 普通模型:编辑和删除
|
||||||
|
actionButtons = `
|
||||||
|
<button onclick="editModel(${item.id})" class="text-primary hover:text-primary/80 mr-3">
|
||||||
|
<i class="fa fa-edit"></i> 编辑
|
||||||
|
</button>
|
||||||
|
<button onclick="deleteModel(${item.id})" class="text-red-500 hover:text-red-600">
|
||||||
|
<i class="fa fa-trash"></i> 删除
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr class="hover:bg-gray-50">
|
<tr class="hover:bg-gray-50 ${isTrained ? 'bg-green-50/30' : ''}">
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
<div class="text-sm font-medium text-gray-900">${item.name || '-'}</div>
|
<div class="text-sm font-medium text-gray-900">${item.name || '-'}${trainedBadge}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
<span class="px-2 py-1 text-xs font-medium rounded bg-blue-100 text-blue-700">${typeDisplay}</span>
|
<span class="px-2 py-1 text-xs font-medium rounded bg-blue-100 text-blue-700">${typeDisplay}</span>
|
||||||
@@ -444,18 +565,16 @@
|
|||||||
<span class="px-2 py-1 text-xs font-medium rounded bg-gray-100 text-gray-700">${sourceDisplay}</span>
|
<span class="px-2 py-1 text-xs font-medium rounded bg-gray-100 text-gray-700">${sourceDisplay}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
<div class="text-sm text-gray-500 max-w-xs truncate">${item.description || '-'}</div>
|
<div class="text-sm text-gray-500 max-w-xs truncate" title="${item.description || ''}">${item.description || '-'}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div class="text-sm text-gray-500 max-w-xs truncate ${isTrained ? 'font-mono text-green-600' : ''}" title="${item.path || ''}">${item.path ? (isTrained ? item.path.split('/').pop() : item.path) : '-'}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
${item.create_time ? new Date(item.create_time).toLocaleString('zh-CN') : '-'}
|
${item.create_time ? new Date(item.create_time).toLocaleString('zh-CN') : '-'}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
<button onclick="editModel(${item.id})" class="text-primary hover:text-primary/80 mr-3">
|
${actionButtons}
|
||||||
<i class="fa fa-edit"></i> 编辑
|
|
||||||
</button>
|
|
||||||
<button onclick="deleteModel(${item.id})" class="text-red-500 hover:text-red-600">
|
|
||||||
<i class="fa fa-trash"></i> 删除
|
|
||||||
</button>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
@@ -523,7 +642,57 @@
|
|||||||
|
|
||||||
// 查看已训练模型
|
// 查看已训练模型
|
||||||
function viewTrainedModel(path) {
|
function viewTrainedModel(path) {
|
||||||
alert(`模型路径: ${path}\n\n您可以从此路径加载模型进行推理或评测。`);
|
// 显示模型路径详情
|
||||||
|
const content = `
|
||||||
|
<div class="text-left">
|
||||||
|
<p class="mb-3 text-gray-600">模型路径:</p>
|
||||||
|
<div class="bg-gray-100 p-3 rounded text-sm font-mono break-all mb-3">${path}</div>
|
||||||
|
<p class="text-sm text-gray-500">您可以复制此路径用于推理或评测。</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
// 使用简单的提示框
|
||||||
|
showModal('模型详情', content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制模型路径
|
||||||
|
function copyModelPath(path) {
|
||||||
|
navigator.clipboard.writeText(path).then(() => {
|
||||||
|
showToast('模型路径已复制到剪贴板');
|
||||||
|
}).catch(() => {
|
||||||
|
// 降级方案
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = path;
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
showToast('模型路径已复制到剪贴板');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示弹窗
|
||||||
|
function showModal(title, content) {
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="bg-white rounded-lg p-6 max-w-lg w-full mx-4">
|
||||||
|
<h3 class="text-lg font-medium mb-4">${title}</h3>
|
||||||
|
<div class="mb-4">${content}</div>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<button onclick="this.closest('.fixed').remove()" class="px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300">关闭</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示提示
|
||||||
|
function showToast(message) {
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'fixed bottom-4 right-4 bg-gray-800 text-white px-4 py-2 rounded shadow-lg z-50';
|
||||||
|
toast.textContent = message;
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
setTimeout(() => toast.remove(), 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑模型
|
// 编辑模型
|
||||||
@@ -554,6 +723,7 @@
|
|||||||
|
|
||||||
// 页面加载时初始化
|
// 页面加载时初始化
|
||||||
loadModels();
|
loadModels();
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,26 +5,30 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>训练日志 / 远光软件微调平台</title>
|
<title>训练日志 / 远光软件微调平台</title>
|
||||||
<script src="../lib/tailwindcss/tailwind.js"></script>
|
<script src="../lib/tailwindcss/tailwind.js"></script>
|
||||||
|
<script>
|
||||||
|
// 禁用 Tailwind 开发模式警告
|
||||||
|
if (typeof console !== 'undefined' && console.warn) {
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<script src="../lib/chart.js/chart.min.js"></script>
|
<script src="../lib/chart.js/chart.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// 确保 Chart.js 已加载
|
// 确保 Chart.js 已加载
|
||||||
if (typeof Chart === 'undefined') {
|
if (typeof Chart === 'undefined') {
|
||||||
console.error('Chart.js 未加载,尝试动态加载...');
|
|
||||||
// 备用:尝试动态加载
|
// 备用:尝试动态加载
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.src = '../lib/chart.js/chart.umd.min.js';
|
script.src = '../lib/chart.js/chart.umd.min.js';
|
||||||
script.onload = function() {
|
|
||||||
console.log('Chart.js 动态加载成功');
|
|
||||||
window.chartJsLoaded = true;
|
|
||||||
};
|
|
||||||
script.onerror = function() {
|
script.onerror = function() {
|
||||||
console.error('Chart.js 加载失败');
|
console.error('Chart.js 加载失败');
|
||||||
};
|
};
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
|
||||||
console.log('Chart.js 已加载');
|
|
||||||
window.chartJsLoaded = true;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
@@ -71,7 +75,7 @@
|
|||||||
<h2 class="text-lg font-medium text-gray-800" id="taskName">加载中...</h2>
|
<h2 class="text-lg font-medium text-gray-800" id="taskName">加载中...</h2>
|
||||||
<span id="taskStatus" class="px-3 py-1 rounded-full text-sm bg-gray-100 text-gray-600">加载中</span>
|
<span id="taskStatus" class="px-3 py-1 rounded-full text-sm bg-gray-100 text-gray-600">加载中</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 text-sm">
|
<div class="grid grid-cols-2 md:grid-cols-6 gap-4 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<div class="text-gray-500 text-xs">基础模型</div>
|
<div class="text-gray-500 text-xs">基础模型</div>
|
||||||
<div id="baseModel" class="font-medium text-gray-800">-</div>
|
<div id="baseModel" class="font-medium text-gray-800">-</div>
|
||||||
@@ -88,6 +92,10 @@
|
|||||||
<div class="text-gray-500 text-xs">进程ID</div>
|
<div class="text-gray-500 text-xs">进程ID</div>
|
||||||
<div id="processId" class="font-medium text-gray-800">-</div>
|
<div id="processId" class="font-medium text-gray-800">-</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 text-xs">GPU信息</div>
|
||||||
|
<div id="taskGPU" class="font-medium text-gray-800">-</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-gray-500 text-xs">最后更新</div>
|
<div class="text-gray-500 text-xs">最后更新</div>
|
||||||
<div id="lastUpdate" class="font-medium text-gray-800">-</div>
|
<div id="lastUpdate" class="font-medium text-gray-800">-</div>
|
||||||
@@ -96,21 +104,63 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 训练曲线图表 -->
|
<!-- 训练曲线图表 -->
|
||||||
<div id="chartsContainer" class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
<div id="chartsContainer" class="bg-white rounded-xl shadow-md p-6 mb-6 border border-gray-100">
|
||||||
<h3 class="text-base font-medium text-gray-800 mb-4">训练曲线</h3>
|
<div class="flex items-center mb-4">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div class="flex items-center justify-center w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-purple-500 text-white mr-3">
|
||||||
<div>
|
<i class="fa fa-line-chart"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-base font-semibold text-gray-800">训练实时曲线</h3>
|
||||||
|
<span id="chartUpdateStatus" class="ml-auto text-xs text-gray-400">自动更新中...</span>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<div class="bg-gradient-to-br from-red-50 to-white rounded-lg p-4 border border-red-100">
|
||||||
<canvas id="lossChart" class="w-full h-48"></canvas>
|
<canvas id="lossChart" class="w-full h-48"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="bg-gradient-to-br from-blue-50 to-white rounded-lg p-4 border border-blue-100">
|
||||||
<canvas id="gradNormChart" class="w-full h-48"></canvas>
|
<canvas id="gradNormChart" class="w-full h-48"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="bg-gradient-to-br from-green-50 to-white rounded-lg p-4 border border-green-100">
|
||||||
<canvas id="learningRateChart" class="w-full h-48"></canvas>
|
<canvas id="learningRateChart" class="w-full h-48"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 训练总结 -->
|
||||||
|
<div id="trainSummaryContainer" class="bg-white rounded-xl shadow-md p-6 mb-6 border border-gray-100">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="flex items-center justify-center w-8 h-8 rounded-lg bg-gradient-to-br from-green-500 to-teal-500 text-white mr-3">
|
||||||
|
<i class="fa fa-check-circle"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-base font-semibold text-gray-800">训练总结</h3>
|
||||||
|
<span id="trainSummaryStatus" class="ml-auto text-xs px-2 py-1 rounded-full bg-gray-100 text-gray-500">训练中</span>
|
||||||
|
</div>
|
||||||
|
<div id="trainSummaryContent" class="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||||||
|
<div class="text-center p-3 bg-gray-50 rounded-lg">
|
||||||
|
<div class="text-xs text-gray-500 mb-1">Epoch</div>
|
||||||
|
<div id="summaryEpoch" class="text-lg font-semibold text-gray-800">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-3 bg-gray-50 rounded-lg">
|
||||||
|
<div class="text-xs text-gray-500 mb-1">训练损失</div>
|
||||||
|
<div id="summaryTrainLoss" class="text-lg font-semibold text-gray-800">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-3 bg-gray-50 rounded-lg">
|
||||||
|
<div class="text-xs text-gray-500 mb-1">训练时长</div>
|
||||||
|
<div id="summaryTrainRuntime" class="text-lg font-semibold text-gray-800">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-3 bg-gray-50 rounded-lg">
|
||||||
|
<div class="text-xs text-gray-500 mb-1">样本/秒</div>
|
||||||
|
<div id="summarySamplesPerSec" class="text-lg font-semibold text-gray-800">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-3 bg-gray-50 rounded-lg">
|
||||||
|
<div class="text-xs text-gray-500 mb-1">步/秒</div>
|
||||||
|
<div id="summaryStepsPerSec" class="text-lg font-semibold text-gray-800">-</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="trainSummaryFlos" class="mt-4 text-center text-xs text-gray-400">
|
||||||
|
浮点运算量 (Total FLOPS): <span id="summaryTotalFlos">-</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 日志内容 -->
|
<!-- 日志内容 -->
|
||||||
<div class="bg-white rounded-lg shadow-sm">
|
<div class="bg-white rounded-lg shadow-sm">
|
||||||
<div class="flex items-center justify-between p-4 border-b border-gray-100">
|
<div class="flex items-center justify-between p-4 border-b border-gray-100">
|
||||||
@@ -145,33 +195,36 @@
|
|||||||
// 初始化图表
|
// 初始化图表
|
||||||
function initCharts() {
|
function initCharts() {
|
||||||
if (typeof Chart === 'undefined') {
|
if (typeof Chart === 'undefined') {
|
||||||
console.error('[Charts] Chart 未定义,无法初始化图表');
|
|
||||||
document.getElementById('chartsContainer').innerHTML = '<div class="text-center p-4 text-red-500"><i class="fa fa-exclamation-triangle mr-2"></i>图表库加载失败,请刷新页面重试</div>';
|
document.getElementById('chartsContainer').innerHTML = '<div class="text-center p-4 text-red-500"><i class="fa fa-exclamation-triangle mr-2"></i>图表库加载失败,请刷新页面重试</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Charts] 开始初始化图表...');
|
// 创建渐变填充函数
|
||||||
const commonOptions = {
|
function createGradient(ctx, colorStart, colorEnd) {
|
||||||
responsive: true,
|
const gradient = ctx.createLinearGradient(0, 0, 0, 200);
|
||||||
maintainAspectRatio: false,
|
gradient.addColorStop(0, colorStart);
|
||||||
animation: false,
|
gradient.addColorStop(1, colorEnd);
|
||||||
scales: {
|
return gradient;
|
||||||
x: {
|
}
|
||||||
title: { display: true, text: 'Step' },
|
|
||||||
grid: { color: 'rgba(0,0,0,0.05)' }
|
// 通用样式配置
|
||||||
},
|
const basePointStyle = {
|
||||||
y: {
|
pointRadius: 4,
|
||||||
title: { display: true, text: 'Value' },
|
pointHoverRadius: 6,
|
||||||
grid: { color: 'rgba(0,0,0,0.05)' }
|
pointBackgroundColor: '#fff',
|
||||||
}
|
pointBorderWidth: 2,
|
||||||
},
|
tension: 0.4
|
||||||
plugins: {
|
};
|
||||||
legend: { display: false }
|
|
||||||
}
|
const baseLineStyle = {
|
||||||
|
borderWidth: 2.5,
|
||||||
|
borderCapStyle: 'round',
|
||||||
|
borderJoinStyle: 'round'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Loss 图表
|
// Loss 图表
|
||||||
const lossCtx = document.getElementById('lossChart').getContext('2d');
|
const lossCtx = document.getElementById('lossChart').getContext('2d');
|
||||||
|
const lossGradient = createGradient(lossCtx, 'rgba(239, 68, 68, 0.4)', 'rgba(239, 68, 68, 0.02)');
|
||||||
lossChart = new Chart(lossCtx, {
|
lossChart = new Chart(lossCtx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
@@ -180,20 +233,53 @@
|
|||||||
label: 'Loss',
|
label: 'Loss',
|
||||||
data: lossData.values,
|
data: lossData.values,
|
||||||
borderColor: '#ef4444',
|
borderColor: '#ef4444',
|
||||||
backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
backgroundColor: lossGradient,
|
||||||
fill: true,
|
fill: true,
|
||||||
tension: 0.3,
|
...basePointStyle,
|
||||||
pointRadius: 3
|
...baseLineStyle
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
...commonOptions,
|
responsive: true,
|
||||||
plugins: { ...commonOptions.plugins, title: { display: true, text: 'Loss', color: '#ef4444', font: { size: 14 } } }
|
maintainAspectRatio: false,
|
||||||
|
animation: { duration: 500, easing: 'easeOutQuart' },
|
||||||
|
interaction: { intersect: false, mode: 'index' },
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: '📉 损失值 (Loss)',
|
||||||
|
color: '#ef4444',
|
||||||
|
font: { size: 15, weight: '600' },
|
||||||
|
padding: { bottom: 15 }
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||||
|
titleColor: '#fff',
|
||||||
|
bodyColor: '#fff',
|
||||||
|
padding: 10,
|
||||||
|
cornerRadius: 8,
|
||||||
|
displayColors: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
title: { display: true, text: '训练步数 (Step)', color: '#6b7280', font: { size: 12 } },
|
||||||
|
grid: { color: 'rgba(0,0,0,0.05)', drawBorder: false },
|
||||||
|
ticks: { color: '#9ca3af', font: { size: 11 } }
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
title: { display: true, text: '损失值', color: '#6b7280', font: { size: 12 } },
|
||||||
|
grid: { color: 'rgba(0,0,0,0.05)', drawBorder: false },
|
||||||
|
ticks: { color: '#9ca3af', font: { size: 11 } }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Grad Norm 图表
|
// Grad Norm 图表
|
||||||
const gradNormCtx = document.getElementById('gradNormChart').getContext('2d');
|
const gradNormCtx = document.getElementById('gradNormChart').getContext('2d');
|
||||||
|
const gradNormGradient = createGradient(gradNormCtx, 'rgba(59, 130, 246, 0.4)', 'rgba(59, 130, 246, 0.02)');
|
||||||
gradNormChart = new Chart(gradNormCtx, {
|
gradNormChart = new Chart(gradNormCtx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
@@ -202,20 +288,53 @@
|
|||||||
label: 'Grad Norm',
|
label: 'Grad Norm',
|
||||||
data: gradNormData.values,
|
data: gradNormData.values,
|
||||||
borderColor: '#3b82f6',
|
borderColor: '#3b82f6',
|
||||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
backgroundColor: gradNormGradient,
|
||||||
fill: true,
|
fill: true,
|
||||||
tension: 0.3,
|
...basePointStyle,
|
||||||
pointRadius: 3
|
...baseLineStyle
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
...commonOptions,
|
responsive: true,
|
||||||
plugins: { ...commonOptions.plugins, title: { display: true, text: 'Grad Norm', color: '#3b82f6', font: { size: 14 } } }
|
maintainAspectRatio: false,
|
||||||
|
animation: { duration: 500, easing: 'easeOutQuart' },
|
||||||
|
interaction: { intersect: false, mode: 'index' },
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: '📊 梯度范数 (Grad Norm)',
|
||||||
|
color: '#3b82f6',
|
||||||
|
font: { size: 15, weight: '600' },
|
||||||
|
padding: { bottom: 15 }
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||||
|
titleColor: '#fff',
|
||||||
|
bodyColor: '#fff',
|
||||||
|
padding: 10,
|
||||||
|
cornerRadius: 8,
|
||||||
|
displayColors: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
title: { display: true, text: '训练步数 (Step)', color: '#6b7280', font: { size: 12 } },
|
||||||
|
grid: { color: 'rgba(0,0,0,0.05)', drawBorder: false },
|
||||||
|
ticks: { color: '#9ca3af', font: { size: 11 } }
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
title: { display: true, text: '梯度范数', color: '#6b7280', font: { size: 12 } },
|
||||||
|
grid: { color: 'rgba(0,0,0,0.05)', drawBorder: false },
|
||||||
|
ticks: { color: '#9ca3af', font: { size: 11 } }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Learning Rate 图表
|
// Learning Rate 图表
|
||||||
const lrCtx = document.getElementById('learningRateChart').getContext('2d');
|
const lrCtx = document.getElementById('learningRateChart').getContext('2d');
|
||||||
|
const lrGradient = createGradient(lrCtx, 'rgba(34, 197, 94, 0.4)', 'rgba(34, 197, 94, 0.02)');
|
||||||
learningRateChart = new Chart(lrCtx, {
|
learningRateChart = new Chart(lrCtx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
@@ -224,23 +343,59 @@
|
|||||||
label: 'Learning Rate',
|
label: 'Learning Rate',
|
||||||
data: learningRateData.values,
|
data: learningRateData.values,
|
||||||
borderColor: '#22c55e',
|
borderColor: '#22c55e',
|
||||||
backgroundColor: 'rgba(34, 197, 94, 0.1)',
|
backgroundColor: lrGradient,
|
||||||
fill: true,
|
fill: true,
|
||||||
tension: 0.3,
|
...basePointStyle,
|
||||||
pointRadius: 3
|
...baseLineStyle
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
...commonOptions,
|
responsive: true,
|
||||||
scales: {
|
maintainAspectRatio: false,
|
||||||
...commonOptions.scales,
|
animation: { duration: 500, easing: 'easeOutQuart' },
|
||||||
y: {
|
interaction: { intersect: false, mode: 'index' },
|
||||||
...commonOptions.scales.y,
|
plugins: {
|
||||||
type: 'logarithmic',
|
legend: { display: false },
|
||||||
title: { display: true, text: 'Learning Rate (log)' }
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: '📈 学习率 (Learning Rate)',
|
||||||
|
color: '#22c55e',
|
||||||
|
font: { size: 15, weight: '600' },
|
||||||
|
padding: { bottom: 15 }
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||||
|
titleColor: '#fff',
|
||||||
|
bodyColor: '#fff',
|
||||||
|
padding: 10,
|
||||||
|
cornerRadius: 8,
|
||||||
|
displayColors: false,
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
return '学习率: ' + context.parsed.y.toExponential(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: { ...commonOptions.plugins, title: { display: true, text: 'Learning Rate', color: '#22c55e', font: { size: 14 } } }
|
scales: {
|
||||||
|
x: {
|
||||||
|
title: { display: true, text: '训练步数 (Step)', color: '#6b7280', font: { size: 12 } },
|
||||||
|
grid: { color: 'rgba(0,0,0,0.05)', drawBorder: false },
|
||||||
|
ticks: { color: '#9ca3af', font: { size: 11 } }
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
type: 'logarithmic',
|
||||||
|
title: { display: true, text: '学习率 (对数坐标)', color: '#6b7280', font: { size: 12 } },
|
||||||
|
grid: { color: 'rgba(0,0,0,0.05)', drawBorder: false },
|
||||||
|
ticks: {
|
||||||
|
color: '#9ca3af',
|
||||||
|
font: { size: 11 },
|
||||||
|
callback: function(value) {
|
||||||
|
return value.toExponential(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -288,6 +443,65 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解析训练总结指标
|
||||||
|
function parseTrainSummary(logContent, taskStatus) {
|
||||||
|
const summary = {
|
||||||
|
epoch: '-',
|
||||||
|
train_loss: '-',
|
||||||
|
train_runtime: '-',
|
||||||
|
samples_per_sec: '-',
|
||||||
|
steps_per_sec: '-',
|
||||||
|
total_flos: '-',
|
||||||
|
completed: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查任务是否已完成
|
||||||
|
if (taskStatus && taskStatus.toLowerCase() === 'completed') {
|
||||||
|
summary.completed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 匹配训练总结格式
|
||||||
|
const summaryRegex = /\*\*\*\*\* train metrics \*\*\*\*\*\s*[\s\S]*?epoch\s*=\s*([\d.]+)\s*[\s\S]*?total_flos\s*=\s*([\d.]+)([GMT]?)\s*[\s\S]*?train_loss\s*=\s*([\d.]+)\s*[\s\S]*?train_runtime\s*=\s*([\d:.]+)\s*[\s\S]*?train_samples_per_second\s*=\s*([\d.]+)\s*[\s\S]*?train_steps_per_second\s*=\s*([\d.]+)/;
|
||||||
|
|
||||||
|
const match = logContent.match(summaryRegex);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
summary.epoch = match[1];
|
||||||
|
summary.total_flos = match[2] + match[3];
|
||||||
|
summary.train_loss = match[4];
|
||||||
|
summary.train_runtime = match[5];
|
||||||
|
summary.samples_per_sec = match[6];
|
||||||
|
summary.steps_per_sec = match[7];
|
||||||
|
summary.completed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新UI
|
||||||
|
const summaryEpoch = document.getElementById('summaryEpoch');
|
||||||
|
const summaryTrainLoss = document.getElementById('summaryTrainLoss');
|
||||||
|
const summaryTrainRuntime = document.getElementById('summaryTrainRuntime');
|
||||||
|
const summarySamplesPerSec = document.getElementById('summarySamplesPerSec');
|
||||||
|
const summaryStepsPerSec = document.getElementById('summaryStepsPerSec');
|
||||||
|
const summaryTotalFlos = document.getElementById('summaryTotalFlos');
|
||||||
|
if (summaryEpoch) summaryEpoch.textContent = summary.epoch;
|
||||||
|
if (summaryTrainLoss) summaryTrainLoss.textContent = summary.train_loss;
|
||||||
|
if (summaryTrainRuntime) summaryTrainRuntime.textContent = summary.train_runtime;
|
||||||
|
if (summarySamplesPerSec) summarySamplesPerSec.textContent = summary.samples_per_sec;
|
||||||
|
if (summaryStepsPerSec) summaryStepsPerSec.textContent = summary.steps_per_sec;
|
||||||
|
if (summaryTotalFlos) summaryTotalFlos.textContent = summary.total_flos;
|
||||||
|
|
||||||
|
// 更新状态标签
|
||||||
|
const statusElement = document.getElementById('trainSummaryStatus');
|
||||||
|
if (statusElement) {
|
||||||
|
if (summary.completed) {
|
||||||
|
statusElement.textContent = '已完成';
|
||||||
|
statusElement.className = 'ml-auto text-xs px-2 py-1 rounded-full bg-green-100 text-green-600';
|
||||||
|
} else {
|
||||||
|
statusElement.textContent = '训练中';
|
||||||
|
statusElement.className = 'ml-auto text-xs px-2 py-1 rounded-full bg-blue-100 text-blue-600';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 带超时的 fetch
|
// 带超时的 fetch
|
||||||
async function fetchWithTimeout(url, options = {}, timeout = 10000) {
|
async function fetchWithTimeout(url, options = {}, timeout = 10000) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
@@ -333,21 +547,17 @@
|
|||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
async function init() {
|
async function init() {
|
||||||
console.log('[Init] 开始初始化...');
|
|
||||||
|
|
||||||
taskId = getTaskId();
|
taskId = getTaskId();
|
||||||
console.log('[Init] taskId:', taskId);
|
|
||||||
|
|
||||||
if (!taskId) {
|
if (!taskId) {
|
||||||
document.getElementById('taskName').textContent = '未指定任务ID';
|
const taskNameEl = document.getElementById('taskName');
|
||||||
document.getElementById('logContent').innerHTML = '<span class="text-gray-400">请先从模型调优列表点击查看日志</span>';
|
const logContentEl = document.getElementById('logContent');
|
||||||
|
if (taskNameEl) taskNameEl.textContent = '未指定任务ID';
|
||||||
|
if (logContentEl) logContentEl.innerHTML = '<span class="text-gray-400">请先从模型调优列表点击查看日志</span>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Init] 加载任务信息...');
|
|
||||||
await loadTaskInfo();
|
await loadTaskInfo();
|
||||||
|
|
||||||
console.log('[Init] 加载日志内容...');
|
|
||||||
await loadLogContent();
|
await loadLogContent();
|
||||||
|
|
||||||
// 自动刷新(每5秒)
|
// 自动刷新(每5秒)
|
||||||
@@ -360,24 +570,26 @@
|
|||||||
// 加载任务信息
|
// 加载任务信息
|
||||||
async function loadTaskInfo() {
|
async function loadTaskInfo() {
|
||||||
try {
|
try {
|
||||||
console.log('[Task] Fetching task info from:', `${API_BASE}/fine-tune/${taskId}`);
|
|
||||||
const response = await fetchWithTimeout(`${API_BASE}/fine-tune/${taskId}`);
|
const response = await fetchWithTimeout(`${API_BASE}/fine-tune/${taskId}`);
|
||||||
console.log('[Task] Response status:', response.status);
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
console.log('[Task] API result:', result);
|
|
||||||
|
|
||||||
if (result.code === 0 && result.data) {
|
if (result.code === 0 && result.data) {
|
||||||
taskInfo = result.data;
|
taskInfo = result.data;
|
||||||
console.log('[Task] taskInfo:', taskInfo);
|
|
||||||
console.log('[Task] process_id:', taskInfo.process_id);
|
|
||||||
await updateTaskInfo();
|
await updateTaskInfo();
|
||||||
} else {
|
} else {
|
||||||
console.error('[Task] API返回错误:', result.message);
|
const statusElement = document.getElementById('taskStatus');
|
||||||
|
if (statusElement) {
|
||||||
|
statusElement.textContent = '获取失败';
|
||||||
|
statusElement.className = 'px-3 py-1 rounded-full text-sm bg-red-100 text-red-700';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Task] 获取任务信息失败:', error);
|
console.error('[Task] 获取任务信息失败:', error);
|
||||||
document.getElementById('taskStatus').textContent = '获取失败';
|
const statusElement = document.getElementById('taskStatus');
|
||||||
document.getElementById('taskStatus').className = 'px-3 py-1 rounded-full text-sm bg-red-100 text-red-700';
|
if (statusElement) {
|
||||||
|
statusElement.textContent = '获取失败';
|
||||||
|
statusElement.className = 'px-3 py-1 rounded-full text-sm bg-red-100 text-red-700';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,10 +597,15 @@
|
|||||||
async function updateTaskInfo() {
|
async function updateTaskInfo() {
|
||||||
if (!taskInfo) return;
|
if (!taskInfo) return;
|
||||||
|
|
||||||
document.getElementById('taskName').textContent = taskInfo.name || '未知任务';
|
const taskNameElement = document.getElementById('taskName');
|
||||||
|
if (taskNameElement) {
|
||||||
|
taskNameElement.textContent = taskInfo.name || '未知任务';
|
||||||
|
}
|
||||||
|
|
||||||
// 更新状态
|
// 更新状态
|
||||||
const statusElement = document.getElementById('taskStatus');
|
const statusElement = document.getElementById('taskStatus');
|
||||||
|
if (!statusElement) return;
|
||||||
|
|
||||||
const actualStatus = taskInfo.status ? taskInfo.status.toLowerCase() : 'unknown';
|
const actualStatus = taskInfo.status ? taskInfo.status.toLowerCase() : 'unknown';
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'pending': { text: '等待中', class: 'bg-gray-100 text-gray-600' },
|
'pending': { text: '等待中', class: 'bg-gray-100 text-gray-600' },
|
||||||
@@ -402,6 +619,39 @@
|
|||||||
statusElement.textContent = statusConfig.text;
|
statusElement.textContent = statusConfig.text;
|
||||||
statusElement.className = `px-3 py-1 rounded-full text-sm ${statusConfig.class}`;
|
statusElement.className = `px-3 py-1 rounded-full text-sm ${statusConfig.class}`;
|
||||||
|
|
||||||
|
// 更新训练总结状态
|
||||||
|
const summaryStatusElement = document.getElementById('trainSummaryStatus');
|
||||||
|
if (summaryStatusElement) {
|
||||||
|
if (actualStatus === 'completed') {
|
||||||
|
summaryStatusElement.textContent = '已完成';
|
||||||
|
summaryStatusElement.className = 'ml-auto text-xs px-2 py-1 rounded-full bg-green-100 text-green-600';
|
||||||
|
} else if (actualStatus === 'running') {
|
||||||
|
summaryStatusElement.textContent = '训练中';
|
||||||
|
summaryStatusElement.className = 'ml-auto text-xs px-2 py-1 rounded-full bg-blue-100 text-blue-600';
|
||||||
|
} else if (actualStatus === 'failed' || actualStatus === 'stopped') {
|
||||||
|
summaryStatusElement.textContent = '已停止';
|
||||||
|
summaryStatusElement.className = 'ml-auto text-xs px-2 py-1 rounded-full bg-gray-100 text-gray-500';
|
||||||
|
} else {
|
||||||
|
summaryStatusElement.textContent = '等待中';
|
||||||
|
summaryStatusElement.className = 'ml-auto text-xs px-2 py-1 rounded-full bg-gray-100 text-gray-500';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新图表区域状态显示
|
||||||
|
const chartStatusElement = document.getElementById('chartUpdateStatus');
|
||||||
|
if (chartStatusElement) {
|
||||||
|
if (actualStatus === 'completed') {
|
||||||
|
chartStatusElement.textContent = '已完成';
|
||||||
|
chartStatusElement.className = 'ml-auto text-xs text-green-500';
|
||||||
|
} else if (actualStatus === 'running') {
|
||||||
|
chartStatusElement.textContent = '自动更新中...';
|
||||||
|
chartStatusElement.className = 'ml-auto text-xs text-gray-400';
|
||||||
|
} else {
|
||||||
|
chartStatusElement.textContent = '-';
|
||||||
|
chartStatusElement.className = 'ml-auto text-xs text-gray-400';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 更新进度
|
// 更新进度
|
||||||
const progressElement = document.getElementById('taskProgress');
|
const progressElement = document.getElementById('taskProgress');
|
||||||
if (progressElement && taskInfo.progress !== undefined) {
|
if (progressElement && taskInfo.progress !== undefined) {
|
||||||
@@ -419,7 +669,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('[Task] 获取GPU信息失败:', e);
|
// GPU信息获取失败,静默处理
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新数据集信息
|
// 更新数据集信息
|
||||||
@@ -452,9 +702,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 其他信息
|
// 其他信息
|
||||||
document.getElementById('processId').textContent = taskInfo.process_id || '-';
|
const processIdElement = document.getElementById('processId');
|
||||||
document.getElementById('createTime').textContent = taskInfo.create_time ?
|
if (processIdElement) {
|
||||||
new Date(taskInfo.create_time).toLocaleString('zh-CN') : '-';
|
processIdElement.textContent = taskInfo.process_id || '-';
|
||||||
|
}
|
||||||
|
const createTimeElement = document.getElementById('createTime');
|
||||||
|
if (createTimeElement) {
|
||||||
|
createTimeElement.textContent = taskInfo.create_time ?
|
||||||
|
new Date(taskInfo.create_time).toLocaleString('zh-CN') : '-';
|
||||||
|
}
|
||||||
|
|
||||||
// 获取模型名称
|
// 获取模型名称
|
||||||
if (taskInfo.base_model) {
|
if (taskInfo.base_model) {
|
||||||
@@ -464,31 +720,33 @@
|
|||||||
|
|
||||||
// 加载模型名称
|
// 加载模型名称
|
||||||
async function loadModelName(modelId) {
|
async function loadModelName(modelId) {
|
||||||
|
const baseModelElement = document.getElementById('baseModel');
|
||||||
|
if (!baseModelElement) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetchWithTimeout(`${API_BASE}/model-manage`);
|
const response = await fetchWithTimeout(`${API_BASE}/model-manage`);
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result.code === 0 && result.data) {
|
if (result.code === 0 && result.data) {
|
||||||
const model = result.data.find(m => m.id == modelId);
|
const model = result.data.find(m => m.id == modelId);
|
||||||
document.getElementById('baseModel').textContent = model ? model.name : `模型${modelId}`;
|
baseModelElement.textContent = model ? model.name : `模型${modelId}`;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
document.getElementById('baseModel').textContent = `模型${modelId}`;
|
baseModelElement.textContent = `模型${modelId}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载日志内容
|
// 加载日志内容
|
||||||
async function loadLogContent() {
|
async function loadLogContent() {
|
||||||
console.log('[Log] loadLogContent called');
|
const logContentElement = document.getElementById('logContent');
|
||||||
console.log('[Log] taskInfo:', taskInfo);
|
|
||||||
console.log('[Log] taskInfo.process_id:', taskInfo ? taskInfo.process_id : 'taskInfo is null');
|
|
||||||
|
|
||||||
// 检查 taskInfo 是否存在
|
// 检查 taskInfo 是否存在
|
||||||
if (!taskInfo) {
|
if (!taskInfo) {
|
||||||
console.log('[Log] taskInfo 为空,等待任务信息加载...');
|
|
||||||
// 尝试重新加载任务信息
|
// 尝试重新加载任务信息
|
||||||
await loadTaskInfo();
|
await loadTaskInfo();
|
||||||
if (!taskInfo) {
|
if (!taskInfo) {
|
||||||
document.getElementById('logContent').innerHTML = '<span class="text-gray-400">无法获取任务信息</span>';
|
if (logContentElement) {
|
||||||
|
logContentElement.innerHTML = '<span class="text-gray-400">无法获取任务信息</span>';
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -499,25 +757,23 @@
|
|||||||
|
|
||||||
if (!processId && !taskName) {
|
if (!processId && !taskName) {
|
||||||
const msg = '<span class="text-gray-400">暂无日志文件 (任务未开始或无进程ID)</span>';
|
const msg = '<span class="text-gray-400">暂无日志文件 (任务未开始或无进程ID)</span>';
|
||||||
document.getElementById('logContent').innerHTML = msg;
|
if (logContentElement) {
|
||||||
|
logContentElement.innerHTML = msg;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('[Log] Fetching training log files...');
|
|
||||||
const response = await fetchWithTimeout(`${API_BASE}/training-log-files`);
|
const response = await fetchWithTimeout(`${API_BASE}/training-log-files`);
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.code === 0 && result.data) {
|
if (result.code === 0 && result.data) {
|
||||||
console.log('[Log] Training log files:', result.data);
|
|
||||||
|
|
||||||
// 优先使用进程ID匹配文件名
|
// 优先使用进程ID匹配文件名
|
||||||
let selectedFile = null;
|
let selectedFile = null;
|
||||||
|
|
||||||
if (processId) {
|
if (processId) {
|
||||||
const pidStr = processId.toString();
|
const pidStr = processId.toString();
|
||||||
for (const file of result.data) {
|
for (const file of result.data) {
|
||||||
console.log(`[Log] Checking file: ${file.file}, PID: ${file.pid}, Match: ${file.file.startsWith(pidStr + '_') || file.file.includes(pidStr)}`);
|
|
||||||
if (file.file.startsWith(pidStr + '_') || file.file.includes(`_${pidStr}_`) || file.file.endsWith(`_${pidStr}.log`)) {
|
if (file.file.startsWith(pidStr + '_') || file.file.includes(`_${pidStr}_`) || file.file.endsWith(`_${pidStr}.log`)) {
|
||||||
selectedFile = file.file;
|
selectedFile = file.file;
|
||||||
break;
|
break;
|
||||||
@@ -538,60 +794,73 @@
|
|||||||
// 如果仍然没有找到,使用第一个文件
|
// 如果仍然没有找到,使用第一个文件
|
||||||
if (!selectedFile && result.data.length > 0) {
|
if (!selectedFile && result.data.length > 0) {
|
||||||
selectedFile = result.data[0].file;
|
selectedFile = result.data[0].file;
|
||||||
console.log('[Log] No matching file found, using first available file:', selectedFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedFile) {
|
if (selectedFile) {
|
||||||
console.log('[Log] Selected log file:', selectedFile);
|
|
||||||
await loadLogFileContent(selectedFile);
|
await loadLogFileContent(selectedFile);
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('logContent').innerHTML = '<span class="text-gray-400">未找到匹配的日志文件</span>';
|
if (logContentElement) {
|
||||||
|
logContentElement.innerHTML = '<span class="text-gray-400">未找到匹配的日志文件</span>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('logContent').innerHTML = '<span class="text-gray-400">获取日志列表失败: ' + (result.message || '未知错误') + '</span>';
|
if (logContentElement) {
|
||||||
|
logContentElement.innerHTML = '<span class="text-gray-400">获取日志列表失败: ' + (result.message || '未知错误') + '</span>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Log] 获取日志列表失败:', error);
|
console.error('[Log] 获取日志列表失败:', error);
|
||||||
document.getElementById('logContent').innerHTML = '<span class="text-red-500">加载日志失败: ' + error.message + '</span>';
|
if (logContentElement) {
|
||||||
|
logContentElement.innerHTML = '<span class="text-red-500">加载日志失败: ' + error.message + '</span>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载日志文件内容
|
// 加载日志文件内容
|
||||||
async function loadLogFileContent(fileName) {
|
async function loadLogFileContent(fileName) {
|
||||||
console.log('[Log] Loading log file:', fileName);
|
const logContentElement = document.getElementById('logContent');
|
||||||
try {
|
try {
|
||||||
const response = await fetchWithTimeout(`${API_BASE}/training-log-content?file=${encodeURIComponent(fileName)}`);
|
const response = await fetchWithTimeout(`${API_BASE}/training-log-content?file=${encodeURIComponent(fileName)}`);
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
console.log('[Log] Log content API response:', result);
|
|
||||||
|
|
||||||
if (result.code === 0 && result.data) {
|
if (result.code === 0 && result.data) {
|
||||||
trainingLogFullContent = result.data.content || '';
|
trainingLogFullContent = result.data.content || '';
|
||||||
console.log('[Log] Log content length:', trainingLogFullContent.length);
|
|
||||||
renderLogContent();
|
renderLogContent();
|
||||||
// 解析并更新图表
|
// 解析并更新图表
|
||||||
parseMetricsFromLog(trainingLogFullContent);
|
parseMetricsFromLog(trainingLogFullContent);
|
||||||
|
// 解析并更新训练总结
|
||||||
|
const taskStatus = taskInfo ? taskInfo.status : 'running';
|
||||||
|
parseTrainSummary(trainingLogFullContent, taskStatus);
|
||||||
} else if (result.code === 2) {
|
} else if (result.code === 2) {
|
||||||
// 文件被锁定,正在训练中
|
// 文件被锁定,正在训练中
|
||||||
document.getElementById('logContent').innerHTML = `
|
if (logContentElement) {
|
||||||
<div class="text-orange-500 p-4 text-center">
|
logContentElement.innerHTML = `
|
||||||
<i class="fa fa-spinner fa-spin fa-2x mb-2"></i>
|
<div class="text-orange-500 p-4 text-center">
|
||||||
<p class="text-lg">日志文件正在被训练进程占用</p>
|
<i class="fa fa-spinner fa-spin fa-2x mb-2"></i>
|
||||||
<p class="text-sm text-gray-500 mt-1">${result.message || '训练结束后可查看完整内容'}</p>
|
<p class="text-lg">日志文件正在被训练进程占用</p>
|
||||||
<p class="text-xs text-gray-400 mt-2">页面将自动刷新...</p>
|
<p class="text-sm text-gray-500 mt-1">${result.message || '训练结束后可查看完整内容'}</p>
|
||||||
</div>
|
<p class="text-xs text-gray-400 mt-2">页面将自动刷新...</p>
|
||||||
`;
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('logContent').innerHTML = '<span class="text-red-500">加载日志失败: ' + (result.message || '未知错误') + '</span>';
|
if (logContentElement) {
|
||||||
|
logContentElement.innerHTML = '<span class="text-red-500">加载日志失败: ' + (result.message || '未知错误') + '</span>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Log] 获取日志内容失败:', error);
|
console.error('[Log] 获取日志内容失败:', error);
|
||||||
document.getElementById('logContent').innerHTML = '<span class="text-red-500">加载日志失败: ' + error.message + '</span>';
|
if (logContentElement) {
|
||||||
|
logContentElement.innerHTML = '<span class="text-red-500">加载日志失败: ' + error.message + '</span>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染日志内容
|
// 渲染日志内容
|
||||||
function renderLogContent() {
|
function renderLogContent() {
|
||||||
const logContent = document.getElementById('logContent');
|
const logContent = document.getElementById('logContent');
|
||||||
|
if (!logContent) return;
|
||||||
|
|
||||||
const searchInput = document.getElementById('logSearchInput');
|
const searchInput = document.getElementById('logSearchInput');
|
||||||
const searchText = searchInput ? searchInput.value.toLowerCase() : '';
|
const searchText = searchInput ? searchInput.value.toLowerCase() : '';
|
||||||
|
|
||||||
@@ -648,9 +917,6 @@
|
|||||||
|
|
||||||
// 搜索日志
|
// 搜索日志
|
||||||
function searchLog() {
|
function searchLog() {
|
||||||
console.log('[Search] 搜索触发,trainingLogFullContent:', trainingLogFullContent ? '已加载' : '未加载');
|
|
||||||
const searchInput = document.getElementById('logSearchInput');
|
|
||||||
console.log('[Search] 搜索文本:', searchInput ? searchInput.value : '输入框未找到');
|
|
||||||
renderLogContent();
|
renderLogContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,24 +929,18 @@
|
|||||||
|
|
||||||
// 页面加载完成后初始化
|
// 页面加载完成后初始化
|
||||||
function startApp() {
|
function startApp() {
|
||||||
console.log('[App] startApp called');
|
|
||||||
console.log('[App] Chart available:', typeof Chart !== 'undefined');
|
|
||||||
|
|
||||||
// 等待 Chart.js 加载完成(最多等待5秒)
|
// 等待 Chart.js 加载完成(最多等待5秒)
|
||||||
let waitCount = 0;
|
let waitCount = 0;
|
||||||
const maxWait = 50; // 50 * 100ms = 5秒
|
const maxWait = 50; // 50 * 100ms = 5秒
|
||||||
|
|
||||||
function waitForChart() {
|
function waitForChart() {
|
||||||
if (typeof Chart !== 'undefined') {
|
if (typeof Chart !== 'undefined') {
|
||||||
console.log('[App] Chart.js 已加载,开始初始化');
|
|
||||||
initCharts();
|
initCharts();
|
||||||
init();
|
init();
|
||||||
} else if (waitCount < maxWait) {
|
} else if (waitCount < maxWait) {
|
||||||
waitCount++;
|
waitCount++;
|
||||||
console.log('[App] 等待 Chart.js 加载... (' + waitCount + ')');
|
|
||||||
setTimeout(waitForChart, 100);
|
setTimeout(waitForChart, 100);
|
||||||
} else {
|
} else {
|
||||||
console.error('[App] Chart.js 加载超时');
|
|
||||||
document.getElementById('chartsContainer').innerHTML = '<div class="text-center p-4 text-red-500"><i class="fa fa-exclamation-triangle mr-2"></i>图表库加载失败,请检查网络或刷新页面</div>';
|
document.getElementById('chartsContainer').innerHTML = '<div class="text-center p-4 text-red-500"><i class="fa fa-exclamation-triangle mr-2"></i>图表库加载失败,请检查网络或刷新页面</div>';
|
||||||
// 仍然初始化其他功能
|
// 仍然初始化其他功能
|
||||||
init();
|
init();
|
||||||
@@ -692,7 +952,6 @@
|
|||||||
initCharts();
|
initCharts();
|
||||||
init();
|
init();
|
||||||
} else {
|
} else {
|
||||||
console.log('[App] Chart.js 尚未加载,开始等待...');
|
|
||||||
setTimeout(waitForChart, 100);
|
setTimeout(waitForChart, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -709,7 +968,6 @@
|
|||||||
fetch(`${API_BASE}/fine-tune/tensorboard/start`, { method: 'POST' })
|
fetch(`${API_BASE}/fine-tune/tensorboard/start`, { method: 'POST' })
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(result => {
|
.then(result => {
|
||||||
console.log('TensorBoard启动结果:', result);
|
|
||||||
if (result.code === 0) {
|
if (result.code === 0) {
|
||||||
// 跳转到TensorBoard页面
|
// 跳转到TensorBoard页面
|
||||||
window.open(TB_URL, '_blank');
|
window.open(TB_URL, '_blank');
|
||||||
@@ -722,7 +980,6 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error('启动TensorBoard失败:', err);
|
|
||||||
alert('提示: 启动失败 - ' + err.message);
|
alert('提示: 启动失败 - ' + err.message);
|
||||||
btn.innerHTML = '<i class="fa fa-bar-chart mr-1"></i>TensorBoard';
|
btn.innerHTML = '<i class="fa fa-bar-chart mr-1"></i>TensorBoard';
|
||||||
btn.className = 'bg-purple-500 text-white px-4 py-2 rounded hover:bg-purple-600 transition-colors text-sm';
|
btn.className = 'bg-purple-500 text-white px-4 py-2 rounded hover:bg-purple-600 transition-colors text-sm';
|
||||||
|
|||||||
Reference in New Issue
Block a user