Files
YG_FT_Platform/web/pages/main.html

1291 lines
79 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模型调优 - 远光软件微调平台</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#1890ff',
sidebarBg: '#001529',
sidebarText: '#bfcbd9',
headerBg: '#fff',
tableHeader: '#fafafa',
danger: '#f5222d',
success: '#52c41a',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.sidebar-item-active {
@apply bg-primary/10 text-primary border-l-4 border-primary;
}
.table-row-hover {
@apply hover:bg-gray-50 transition-colors;
}
.sidebar-section-title {
@apply px-4 py-2 text-xs text-sidebarText/70 font-medium uppercase tracking-wider;
}
.card-radio {
@apply border border-gray-200 rounded-lg p-4 cursor-pointer transition-all;
}
.card-radio.active {
@apply border-primary bg-blue-50;
}
.card-radio:hover {
@apply border-gray-300;
}
}
</style>
</head>
<body class="antialiased bg-gray-50 flex h-screen overflow-hidden">
<!-- 侧边导航 -->
<aside class="w-64 bg-sidebarBg text-sidebarText flex-shrink-0 hidden md:block flex flex-col h-full">
<!-- 平台LOGO区域 -->
<div class="p-4 border-b border-sidebarBg/30 flex items-center">
<img src="../assets/logo/logo.png" alt="Logo" class="w-6 h-6 object-contain mr-2">
<span class="text-white font-medium">远光软件微调平台</span>
</div>
<!-- 导航主区域 -->
<nav class="flex-1 overflow-y-auto py-2">
<!-- 第一分区:模型服务 -->
<div class="sidebar-section-title">模型服务</div>
<a href="#" data-page="fine-tune" class="nav-link flex items-center px-4 py-2.5 hover:bg-sidebarBg/20 transition-colors">
<i class="fa fa-cogs w-5 text-center"></i>
<span class="ml-2">模型调优</span>
</a>
<a href="#" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-sidebarBg/20 transition-colors">
<i class="fa fa-database w-5 text-center"></i>
<span class="ml-2">我的模型</span>
</a>
<a href="#" data-page="model-eval" class="nav-link flex items-center px-4 py-2.5 hover:bg-sidebarBg/20 transition-colors">
<i class="fa fa-line-chart w-5 text-center"></i>
<span class="ml-2">模型评测</span>
</a>
<a href="#" data-page="model-deploy" class="nav-link flex items-center px-4 py-2.5 hover:bg-sidebarBg/20 transition-colors">
<i class="fa fa-server w-5 text-center"></i>
<span class="ml-2">模型部署</span>
</a>
<!-- 第二分区:资源管理 -->
<div class="sidebar-section-title mt-6">资源管理</div>
<a href="#" data-page="model-manage" class="nav-link flex items-center px-4 py-2.5 hover:bg-sidebarBg/20 transition-colors">
<i class="fa fa-cube w-5 text-center"></i>
<span class="ml-2">模型管理</span>
</a>
<a href="#" data-page="dataset-manage" class="nav-link flex items-center px-4 py-2.5 hover:bg-sidebarBg/20 transition-colors">
<i class="fa fa-file-text w-5 text-center"></i>
<span class="ml-2">数据集管理</span>
</a>
<a href="#" data-page="data-generate" class="nav-link flex items-center px-4 py-2.5 hover:bg-sidebarBg/20 transition-colors">
<i class="fa fa-database w-5 text-center"></i>
<span class="ml-2">数据生成</span>
</a>
<!-- 第三分区:系统设置 -->
<div class="sidebar-section-title mt-6">系统设置</div>
<a href="#" data-page="config" class="nav-link flex items-center px-4 py-2.5 hover:bg-sidebarBg/20 transition-colors">
<i class="fa fa-bar-chart w-5 text-center"></i>
<span class="ml-2">平台性能</span>
</a>
</nav>
<!-- 底部信息区域 -->
<div class="p-4 border-t border-sidebarBg/30 text-xs mt-auto">
<div class="mb-2 text-sidebarText/80">默认业务空间</div>
<div class="flex items-center justify-between">
<span class="text-sidebarText">版本 v1.0.0</span>
<i class="fa fa-question-circle-o text-sidebarText/70"></i>
</div>
</div>
</aside>
<!-- 主内容区 -->
<div class="flex-1 flex flex-col overflow-hidden">
<!-- 顶部导航 -->
<header class="bg-headerBg border-b border-gray-200 shadow-sm">
<div class="flex items-center justify-between px-6 h-14">
<div class="flex items-center space-x-6">
<button class="md:hidden text-gray-500 hover:text-gray-700">
<i class="fa fa-bars"></i>
</button>
</div>
<div class="flex items-center space-x-4">
<div class="relative group">
<img src="https://picsum.photos/id/1005/32/32" class="w-8 h-8 rounded-full cursor-pointer" alt="用户头像">
<div class="absolute right-0 top-full mt-2 bg-white rounded shadow-lg py-1 hidden group-hover:block border border-gray-100 min-w-[140px]">
<a href="login.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 whitespace-nowrap">
<i class="fa fa-sign-out mr-1"></i>退出登录
</a>
</div>
</div>
</div>
</div>
</header>
<!-- 内容区域 -->
<main class="flex-1 overflow-y-auto p-6 bg-gray-50">
<div id="page-content">
<!-- 内容由 JavaScript 动态加载 -->
</div>
</main>
</div>
<script>
// 动态获取 API 基础地址(根据当前访问的 IP 自动调整)
const getApiBase = () => {
const protocol = window.location.protocol;
const hostname = window.location.hostname;
return `${protocol}//${hostname}:8080/api`;
};
const API_BASE = getApiBase();
// 各功能模块的表格配置
const tableConfigs = {
'fine-tune': {
title: '模型调优',
api: 'fine-tune',
hasCreate: true,
createText: '创建训练任务',
columns: [
{ title: '任务名称', key: 'name' },
{ title: '基础模型', key: 'base_model' },
{ title: '状态', key: 'status', render: (val) => `<span class="px-2 py-1 rounded text-xs ${val === 'running' ? 'bg-green-100 text-green-700' : val === 'failed' ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-700'}">${val}</span>` },
{ title: '进度', key: 'progress', render: (val) => `${val || 0}%` },
{ title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' }
],
actions: ['stop', 'logs', 'delete']
},
'my-models': {
title: '我的模型',
api: 'my-models',
hasCreate: false,
columns: [
{ title: '模型名称', key: 'name' },
{ title: '类型', key: 'type' },
{ title: '版本', key: 'version' },
{ title: '描述', key: 'description' },
{ title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' }
],
actions: ['deploy', 'eval', 'delete']
},
'model-eval': {
title: '模型评测',
api: 'model-eval',
hasCreate: true,
createText: '新建评测',
columns: [
{ title: '模型名称', key: 'model_name' },
{ title: '数据集', key: 'dataset' },
{ title: '指标', key: 'metric' },
{ title: '得分', key: 'score', render: (val) => val ? `${val}%` : '-' },
{ title: '状态', key: 'status' },
{ title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' }
],
actions: ['report', 'delete']
},
'model-deploy': {
title: '模型部署',
api: 'model-deploy',
hasCreate: true,
createText: '新建部署',
columns: [
{ title: '服务名称', key: 'model_name' },
{ title: '端点', key: 'endpoint' },
{ title: '实例', key: 'instance' },
{ title: '状态', key: 'status', render: (val) => `<span class="px-2 py-1 rounded text-xs ${val === 'running' ? 'bg-green-100 text-green-700' : val === 'stopped' ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-700'}">${val}</span>` },
{ title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' }
],
actions: ['stop', 'scale', 'delete']
},
'dataset-manage': {
title: '数据集管理',
api: 'dataset-manage',
hasCreate: true,
createText: '上传数据集',
columns: [
{ title: '数据集名称', key: 'name' },
{ title: '类型', key: 'type' },
{ title: '大小', key: 'size' },
{ title: '样本数', key: 'count' },
{ title: '描述', key: 'description' },
{ title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' }
],
actions: ['preview', 'download', 'delete']
},
'data-generate': {
title: '数据生成',
api: 'data-generate',
hasCreate: true,
createText: '新建生成任务',
columns: [
{ title: '任务名称', key: 'name' },
{ title: '模板', key: 'template' },
{ title: '生成数', key: 'count' },
{ title: '状态', key: 'status' },
{ title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' }
],
actions: ['stop', 'detail', 'delete']
},
'model-manage': {
title: '模型管理',
api: 'model-manage',
hasCreate: true,
createText: '添加模型',
columns: [
{ title: '模型名称', key: 'name' },
{ title: '模型类型', key: 'type' },
{ title: '模型版本', key: 'version' },
{ title: '模型路径', key: 'path', render: (val) => val || '-' },
{ title: '描述', key: 'description', render: (val) => val || '-' },
{ title: '创建时间', key: 'create_time', render: (val) => val ? new Date(val).toLocaleString('zh-CN') : '-' }
],
actions: ['edit', 'delete']
},
'config': {
title: '平台性能',
api: 'sys-config',
hasCreate: false,
isForm: true,
formFields: [
{ name: 'config_key', label: '配置键', type: 'text' },
{ name: 'config_value', label: '配置值', type: 'text' },
{ name: 'description', label: '描述', type: 'text' }
]
}
};
// 操作按钮映射
const actionLabels = {
'stop': '停止',
'logs': '查看日志',
'delete': '删除',
'deploy': '部署',
'eval': '评测',
'report': '查看报告',
'scale': '扩容',
'preview': '预览',
'download': '下载',
'detail': '详情',
'edit': '编辑'
};
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 检查URL参数
const urlParams = new URLSearchParams(window.location.search);
const pageParam = urlParams.get('page');
// 加载页面优先使用URL参数
const defaultPage = pageParam || 'fine-tune';
loadPage(defaultPage);
// 更新侧边栏高亮状态
document.querySelectorAll('.nav-link').forEach(link => {
if (link.dataset.page === defaultPage) {
link.classList.add('sidebar-item-active');
link.classList.remove('hover:bg-sidebarBg/20', 'transition-colors');
} else {
link.classList.remove('sidebar-item-active');
link.classList.add('hover:bg-sidebarBg/20', 'transition-colors');
}
});
// 绑定导航点击事件
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const page = this.dataset.page;
loadPage(page);
// 更新高亮状态
document.querySelectorAll('.nav-link').forEach(l => {
l.classList.remove('sidebar-item-active');
l.classList.add('hover:bg-sidebarBg/20', 'transition-colors');
});
this.classList.add('sidebar-item-active');
this.classList.remove('hover:bg-sidebarBg/20', 'transition-colors');
});
});
});
// 加载页面内容
async function loadPage(pageName) {
const container = document.getElementById('page-content');
const config = tableConfigs[pageName];
if (!config) return;
// 显示加载中
container.innerHTML = `
<div class="bg-white rounded-lg shadow-sm mb-6 p-8 text-center">
<i class="fa fa-spinner fa-spin text-3xl text-primary"></i>
<p class="mt-2 text-gray-500">加载中...</p>
</div>
`;
try {
// 获取数据
let data = [];
if (config.isForm) {
// 平台配置页面,直接获取配置列表
data = await fetchData(`${API_BASE}/${config.api}`);
} else {
data = await fetchData(`${API_BASE}/${config.api}`);
}
// 渲染页面
if (config.isForm) {
container.innerHTML = renderConfigPage(config, data);
} else {
container.innerHTML = renderTablePage(config, data);
}
} catch (error) {
console.error('加载数据失败:', error);
container.innerHTML = `
<div class="bg-white rounded-lg shadow-sm mb-6 p-8 text-center">
<i class="fa fa-exclamation-circle text-3xl text-danger"></i>
<p class="mt-2 text-gray-500">加载数据失败,请检查后端服务是否启动</p>
<p class="text-sm text-gray-400 mt-1">${error.message}</p>
</div>
`;
}
}
// 获取 API 数据
async function fetchData(url) {
const response = await fetch(url);
const result = await response.json();
if (result.code !== 0) {
throw new Error(result.message || '获取数据失败');
}
return result.data || [];
}
// 删除数据
async function deleteItem(api, id) {
showConfirm('确认删除', '确定要删除这条记录吗?', async () => {
try {
const response = await fetch(`${API_BASE}/${api}/${id}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.code === 0) {
// 刷新当前页面
const activeLink = document.querySelector('.nav-link.sidebar-item-active');
if (activeLink) {
loadPage(activeLink.dataset.page);
}
} else {
showMessage('错误', result.message || '删除失败', 'error');
}
} catch (error) {
showMessage('错误', '删除失败: ' + error.message, 'error');
}
});
}
// 渲染表格页面
function renderTablePage(config, data) {
const createButton = config.hasCreate ? `
<button onclick="showCreateModal('${config.api}')" class="bg-primary text-white px-3 py-1.5 rounded text-sm hover:bg-primary/90 transition-colors">
<i class="fa fa-plus mr-1"></i>${config.createText}
</button>
` : '';
const columns = config.columns;
const hasData = data && data.length > 0;
return `
<div class="bg-white rounded-lg shadow-sm mb-6">
<div class="flex items-center justify-between p-4 border-b border-gray-100">
<h2 class="text-lg font-medium">${config.title}</h2>
<div class="flex items-center space-x-3">
${createButton}
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="bg-tableHeader text-gray-500 text-sm">
${columns.map(col => `<th class="px-4 py-3 text-left font-medium">${col.title}</th>`).join('')}
<th class="px-4 py-3 text-left font-medium">操作</th>
</tr>
</thead>
<tbody>
${hasData ? data.map(item => `
<tr class="border-b border-gray-100 table-row-hover">
${columns.map(col => `
<td class="px-4 py-4 text-sm">
${col.render ? col.render(item[col.key]) : (item[col.key] || '-')}
</td>
`).join('')}
<td class="px-4 py-4 text-sm">
<div class="flex space-x-2">
${config.actions.map(action => `
<button onclick="${action === 'delete' ? `deleteItem('${config.api}', ${item.id})` : `showMessage('提示', '${actionLabels[action] || action}功能开发中...', 'info')`}"
class="${action === 'delete' ? 'text-danger hover:text-danger/80' : 'text-primary hover:text-primary/80'}">
${actionLabels[action] || action}
</button>
`).join('')}
</div>
</td>
</tr>
`).join('') : ''}
</tbody>
</table>
</div>
${!hasData ? `
<div class="p-8 text-center text-gray-400">
<div class="py-12">
<i class="fa fa-inbox text-5xl mb-4 text-gray-300"></i>
<p class="text-gray-500">暂无数据</p>
</div>
</div>
` : ''}
</div>
`;
}
// 渲染配置页面
function renderConfigPage(config, data) {
return `
<div class="bg-white rounded-lg shadow-sm mb-6">
<div class="flex items-center justify-between p-4 border-b border-gray-100">
<h2 class="text-lg font-medium">${config.title}</h2>
</div>
<div class="p-6">
<form class="space-y-6" onsubmit="event.preventDefault(); saveConfig()">
<div>
<h3 class="text-base font-medium text-gray-800 mb-4">基本设置</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">平台名称</label>
<input type="text" class="w-full px-3 py-2 border border-gray-300 rounded-lg" value="远光软件微调平台">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">API地址</label>
<input type="text" class="w-full px-3 py-2 border border-gray-300 rounded-lg" placeholder="https://api.example.com">
</div>
</div>
</div>
<div class="border-t border-gray-100 pt-6">
<h3 class="text-base font-medium text-gray-800 mb-4">模型配置</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">默认基础模型</label>
<select class="w-full px-3 py-2 border border-gray-300 rounded-lg">
<option>通义千问3-4B-Instruct</option>
<option>通义千问7B-Instruct</option>
</select>
</div>
</div>
</div>
<div class="border-t border-gray-100 pt-6">
<button type="submit" class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-primary/90">
保存配置
</button>
</div>
</form>
</div>
</div>
`;
}
function saveConfig() {
showMessage('提示', '配置保存功能开发中...', 'info');
}
// 当前页面状态
let currentPage = 'fine-tune';
let currentParentPage = null;
// 显示创建表单页面
function showCreateModal(apiType) {
if (apiType === 'fine-tune') {
window.location.href = 'fine-tune-create.html';
} else if (apiType === 'model-manage') {
window.location.href = 'model-manage-create.html';
} else if (apiType === 'model-eval') {
window.location.href = 'model-eval-create.html';
} else if (apiType === 'dataset-manage') {
window.location.href = 'dataset-create.html';
} else {
showMessage('提示', '该功能开发中...', 'info');
}
}
// 返回列表页
function goBack() {
if (currentParentPage) {
currentPage = currentParentPage;
currentParentPage = null;
loadPage(currentPage);
} else {
loadPage('fine-tune');
}
}
// 渲染创建训练任务页面
function renderFineTuneCreatePage() {
return `
<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">
<a href="#" onclick="goBack()" class="text-gray-500 hover:text-gray-700 flex items-center px-3 py-1.5 rounded hover:bg-gray-100">
<i class="fa fa-arrow-left"></i>
<span class="ml-1">上一步</span>
</a>
</div>
<div class="flex items-center text-sm">
<span class="text-primary cursor-pointer hover:underline" onclick="goBack()">模型调优</span>
<span class="mx-2 text-gray-300">/</span>
<span class="text-gray-800 font-medium">创建训练任务</span>
</div>
</div>
<div class="p-6">
<form id="createForm">
<!-- 基本信息 -->
<div class="mb-6">
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">基本信息</h3>
<div class="flex items-center">
<label class="w-24 text-sm text-gray-600">任务名称</label>
<div class="flex-1 max-w-md">
<input type="text" name="name" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none" placeholder="请输入任务名称" maxlength="50">
<p class="text-xs text-gray-400 mt-1"><span id="nameCount">0</span> / 50</p>
</div>
</div>
</div>
<!-- 训练配置 -->
<div class="mb-10">
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">训练配置</h3>
<div class="mb-6">
<label class="block text-sm text-gray-600 mb-3">训练方式</label>
<div class="grid grid-cols-3 gap-4">
<label class="card-radio active cursor-pointer" data-value="SFT">
<div class="flex items-start">
<input type="radio" name="train_type" value="SFT" checked class="mt-1 mr-2 w-4 h-4 text-primary border-primary focus:ring-primary">
<div>
<div class="font-medium text-sm">SFT 微调训练</div>
<div class="text-xs text-gray-400 mt-1">在监督指令下,增强模型指令跟随的能力</div>
</div>
</div>
</label>
<label class="card-radio cursor-pointer" data-value="DPO">
<div class="flex items-start">
<input type="radio" name="train_type" value="DPO" class="mt-1 mr-2 w-4 h-4 text-primary border-primary focus:ring-primary">
<div>
<div class="font-medium text-sm">DPO 偏好训练</div>
<div class="text-xs text-gray-400 mt-1">引入人类反馈,降低幻觉</div>
</div>
</div>
</label>
<label class="card-radio cursor-pointer" data-value="CPT">
<div class="flex items-start">
<input type="radio" name="train_type" value="CPT" class="mt-1 mr-2 w-4 h-4 text-primary border-primary focus:ring-primary">
<div>
<div class="font-medium text-sm">CPT 继续预训练</div>
<div class="text-xs text-gray-400 mt-1">通过无标注数据进行无监督训练</div>
</div>
</div>
</label>
</div>
</div>
<div class="mb-6">
<label class="block text-sm text-gray-600 mb-3">选择模型</label>
<div class="flex items-center space-x-6 mb-3">
<label class="flex items-center">
<input type="radio" name="model_source" value="preset" checked class="mr-2">
<span class="text-sm">预置模型</span>
</label>
<label class="flex items-center">
<input type="radio" name="model_source" value="custom" class="mr-2">
<span class="text-sm">自定义模型</span>
</label>
</div>
<div>
<select name="base_model" id="baseModelSelect" class="w-96 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none">
<option value="">请选择</option>
<option value="qwen-3-4b-instruct">通义千问3-4B-Instruct</option>
<option value="qwen-7b-instruct">通义千问7B-Instruct</option>
<option value="qwen-14b-instruct">通义千问14B-Instruct</option>
<option value="llama2-7b">Llama2-7B</option>
<option value="llama2-13b">Llama2-13B</option>
</select>
</div>
</div>
<div class="mb-6">
<label class="block text-sm text-gray-600 mb-3">训练方法</label>
<div class="flex items-center space-x-6" id="trainMethodContainer">
<label class="flex items-center opacity-50 cursor-not-allowed" id="trainMethodLora">
<input type="radio" name="train_method" value="lora" class="mr-2" disabled>
<span class="text-sm">高效训练</span>
</label>
<label class="flex items-center opacity-50 cursor-not-allowed" id="trainMethodFull">
<input type="radio" name="train_method" value="full" class="mr-2" disabled>
<span class="text-sm">全参训练</span>
</label>
</div>
</div>
<!-- 参数配置组件 - LoRA高效训练 -->
<div id="paramConfigPanelLora" class="hidden">
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-5">
<div class="flex justify-between items-center mb-4">
<h3 class="text-sm font-semibold text-gray-700">参数配置</h3>
<div class="flex items-center space-x-3">
<button type="button" onclick="resetDefaultConfig('lora')" class="text-gray-500 text-sm flex items-center hover:text-primary transition-colors">
<i class="fa fa-rotate-left mr-1"></i>
<span>还原配置</span>
</button>
<button type="button" onclick="toggleParamTable('lora')" id="toggleParamBtnLora" class="text-primary text-sm flex items-center hover:underline">
<i class="fa fa-chevron-up mr-1"></i>
<span id="toggleParamTextLora">收起配置</span>
</button>
</div>
</div>
<div id="paramTableContainerLora" class="overflow-x-auto">
<table class="w-full border-collapse text-sm">
<thead>
<tr class="bg-blue-50">
<th class="text-left p-3 border border-gray-200 font-medium text-gray-700 w-40">参数名称</th>
<th class="text-left p-3 border border-gray-200 font-medium text-gray-700 w-64">配置</th>
<th class="text-left p-3 border border-gray-200 font-medium text-gray-700">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">batch_size</td>
<td class="p-3 border border-gray-200">
<input type="number" name="batch_size_lora" value="16" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[8,1024], step:8</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">批次大小代表模型训练过程中模型更新模型参数的数据步长可理解为模型每看多少数据即更新一次模型参数一般建议的批次大小为16/32</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">learning_rate</td>
<td class="p-3 border border-gray-200">
<input type="text" name="learning_rate_lora" value="3e-4" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">学习率,代表每次更新数据的增量参数权重,学习率数值越大参数变化越大,对模型影响越大</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">n_epochs</td>
<td class="p-3 border border-gray-200">
<input type="number" name="n_epochs_lora" value="3" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[1,200], step:1</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">循环次数代表模型训练过程中模型学习数据集的次数可理解为看几遍数据一般建议的范围是1-3遍即可</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">eval_steps</td>
<td class="p-3 border border-gray-200">
<input type="number" name="eval_steps_lora" value="50" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[1,2147483647]</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">验证步数,训练阶段针模型的验证间隔步长,用于阶段性评估模型训练准确率、训练损失</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">lora_alpha</td>
<td class="p-3 border border-gray-200">
<input type="number" name="lora_alpha" value="32" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">LoRa阿尔法LoRa训练中的缩放系数用于调整初始化训练权重使其与预训练权重接近或保持一致</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">lora_dropout</td>
<td class="p-3 border border-gray-200">
<input type="number" name="lora_dropout" step="0.01" value="0.1" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[0,0.2]</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">LoRa丢弃率配置训练过程中随机丢弃或忽略神经元的比率防止过拟合提高模型泛化能力</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">lora_rank</td>
<td class="p-3 border border-gray-200">
<input type="number" name="lora_rank" value="8" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">LoRa秩值LoRa训练中的秩大小影响LoRa训练中自身数据对模型作用程度秩越大作用越大需要依据数据量选择合适的秩</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">lr_scheduler_type</td>
<td class="p-3 border border-gray-200">
<select name="lr_scheduler_type_lora" class="w-36 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<option value="linear">linear</option>
<option value="cosine">cosine</option>
<option value="constant">constant</option>
</select>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">学习率调整策略,选择不同的学习率策略,动态地改变模型在训练过程中更新权重时所采用的学习率大小</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">max_length</td>
<td class="p-3 border border-gray-200">
<input type="number" name="max_length_lora" value="8192" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[500,131072]</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">序列长度,单个训练数据样本的最大长度,超出配置长度将丢弃</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">warmup_ratio</td>
<td class="p-3 border border-gray-200">
<input type="number" name="warmup_ratio_lora" step="0.01" value="0.05" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[0,1]</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">学习率预热比例,学习率预热阶段占总训练步数的比例</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">weight_decay</td>
<td class="p-3 border border-gray-200">
<input type="number" name="weight_decay_lora" step="0.01" value="0.01" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[0,0.2]</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">权重衰减用于在优化过程中对模型参数施加L2正则化防止过拟合</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 参数配置组件 - 全参训练 -->
<div id="paramConfigPanelFull" class="hidden">
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-5">
<div class="flex justify-between items-center mb-4">
<h3 class="text-sm font-semibold text-gray-700">参数配置</h3>
<div class="flex items-center space-x-3">
<button type="button" onclick="resetDefaultConfig('full')" class="text-gray-500 text-sm flex items-center hover:text-primary transition-colors">
<i class="fa fa-rotate-left mr-1"></i>
<span>还原配置</span>
</button>
<button type="button" onclick="toggleParamTable('full')" id="toggleParamBtnFull" class="text-primary text-sm flex items-center hover:underline">
<i class="fa fa-chevron-up mr-1"></i>
<span id="toggleParamTextFull">收起配置</span>
</button>
</div>
</div>
<div id="paramTableContainerFull" class="overflow-x-auto">
<table class="w-full border-collapse text-sm">
<thead>
<tr class="bg-blue-50">
<th class="text-left p-3 border border-gray-200 font-medium text-gray-700 w-40">参数名称</th>
<th class="text-left p-3 border border-gray-200 font-medium text-gray-700 w-64">配置</th>
<th class="text-left p-3 border border-gray-200 font-medium text-gray-700">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">batch_size</td>
<td class="p-3 border border-gray-200">
<input type="number" name="batch_size_full" value="16" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[8,1024], step:8</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">批次大小代表模型训练过程中模型更新模型参数的数据步长可理解为模型每看多少数据即更新一次模型参数一般建议的批次大小为16/32表示模型每看16或32条数据即更新一次参数</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">learning_rate</td>
<td class="p-3 border border-gray-200">
<input type="text" name="learning_rate_full" value="1e-5" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">学习率,代表每次更新数据的增量参数权重,学习率数值越大参数变化越大,对模型影响越大</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">n_epochs</td>
<td class="p-3 border border-gray-200">
<input type="number" name="n_epochs_full" value="3" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[1,200], step:1</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">循环次数代表模型训练过程中模型学习数据集的次数可理解为看几遍数据一般建议的范围是1-3遍即可可依据需求进行调整</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">eval_steps</td>
<td class="p-3 border border-gray-200">
<input type="number" name="eval_steps_full" value="50" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[1,2147483647]</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">验证步数,训练阶段针模型的验证间隔步长,用于阶段性评估模型训练准确率、训练损失</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">lr_scheduler_type</td>
<td class="p-3 border border-gray-200">
<select name="lr_scheduler_type_full" class="w-36 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<option value="linear">linear</option>
<option value="cosine">cosine</option>
<option value="constant">constant</option>
</select>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">学习率调整策略,选择不同的学习率策略,动态地改变模型在训练过程中更新权重时所采用的学习率大小</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">max_length</td>
<td class="p-3 border border-gray-200">
<input type="number" name="max_length_full" value="8192" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[500,131072]</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">序列长度,单个训练数据样本的最大长度,超出配置长度将丢弃</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">warmup_ratio</td>
<td class="p-3 border border-gray-200">
<input type="number" name="warmup_ratio_full" step="0.01" value="0.05" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[0,1]</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">学习率预热比例,学习率预热阶段占总训练步数的比例</td>
</tr>
<tr>
<td class="p-3 border border-gray-200 text-gray-700">weight_decay</td>
<td class="p-3 border border-gray-200">
<input type="number" name="weight_decay_full" step="0.01" value="0.01" class="w-24 px-2 py-1 border border-gray-300 rounded text-sm focus:border-primary focus:outline-none">
<span class="text-xs text-gray-400 ml-2">[0,0.2]</span>
</td>
<td class="p-3 border border-gray-200 text-gray-500 text-sm">权重衰减用于在优化过程中对模型参数施加L2正则化防止过拟合</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 数据配置 -->
<div class="mb-10">
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">数据配置</h3>
<div class="mb-6">
<label class="block text-sm text-gray-600 mb-3">训练集</label>
<div>
<select name="dataset_id" class="w-96 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none">
<option value="">请选择</option>
</select>
</div>
</div>
<div class="mb-6">
<label class="block text-sm text-gray-600 mb-3">验证集 <span class="text-red-500">*</span></label>
<div class="flex items-center space-x-6 mb-3">
<label class="flex items-center">
<input type="radio" name="valid_split" value="auto" checked class="mr-2" onchange="toggleValidSetPanel('auto')">
<span class="text-sm">自动切分</span>
</label>
<label class="flex items-center">
<input type="radio" name="valid_split" value="custom" class="mr-2" onchange="toggleValidSetPanel('custom')">
<span class="text-sm">选择数据集</span>
</label>
</div>
<!-- 自动切分面板 -->
<div id="validAutoPanel" class="flex items-center">
<span class="text-sm text-gray-600 mr-2">从当前训练集随机分割</span>
<input type="number" name="valid_ratio" value="10" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm text-center focus:border-primary focus:outline-none">
<span class="text-sm text-gray-600 ml-2">% 作为验证集</span>
</div>
<!-- 选择数据集面板 -->
<div id="validCustomPanel" class="hidden">
<select name="valid_dataset_id" id="validDatasetSelect" class="w-96 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none">
<option value="">请选择验证数据集</option>
</select>
</div>
</div>
</div>
<!-- 训练产出 -->
<div class="mb-10">
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">训练产出</h3>
<div class="mb-4">
<label class="block text-sm text-gray-600 mb-3">模型名称</label>
<div>
<input type="text" name="output_model_name" class="w-96 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none" placeholder="请输入模型名称" maxlength="50">
<p class="text-xs text-gray-400 mt-1"><span id="modelNameCount">0</span> / 50</p>
</div>
</div>
<div class="mb-4">
<div class="flex items-center">
<span class="text-sm text-gray-600 mr-2">模型加密</span>
<span class="px-2 py-0.5 bg-green-100 text-green-700 text-xs rounded">安全升级</span>
</div>
<p class="text-xs text-gray-400 mt-1">为保障您的数据安全,平台会为导出的模型文件开启 OSS 服务端加密</p>
</div>
</div>
<!-- 底部按钮 -->
<div class="flex items-center justify-between pt-6 border-t border-gray-100">
<div class="flex items-center space-x-3">
<button type="button" onclick="submitCreateForm()" class="px-4 py-2 bg-primary text-white rounded-lg text-sm hover:bg-primary/90">
开始训练
</button>
<button type="button" onclick="goBack()" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300">
取消
</button>
</div>
<div class="flex items-center text-sm">
<a href="#" class="text-primary hover:underline">训练费用 (预估)</a>
<span class="mx-2 text-gray-300">|</span>
<a href="#" class="text-primary hover:underline">计算详情</a>
</div>
</div>
</form>
</div>
</div>
`;
}
// 绑定创建页面事件
function bindCreatePageEvents() {
// 卡片式单选框
document.querySelectorAll('.card-radio').forEach(card => {
card.addEventListener('click', () => {
const parent = card.parentElement;
parent.querySelectorAll('.card-radio').forEach(c => c.classList.remove('active'));
card.classList.add('active');
card.querySelector('input').checked = true;
});
});
// 任务名称字数统计
const nameInput = document.querySelector('input[name="name"]');
if (nameInput) {
nameInput.addEventListener('input', () => {
const countEl = document.getElementById('nameCount');
if (countEl) countEl.textContent = nameInput.value.length;
});
}
// 模型名称字数统计
const modelNameInput = document.querySelector('input[name="output_model_name"]');
if (modelNameInput) {
modelNameInput.addEventListener('input', () => {
const countEl = document.getElementById('modelNameCount');
if (countEl) countEl.textContent = modelNameInput.value.length;
});
}
// 选择模型后启用训练方法
const baseModelSelect = document.getElementById('baseModelSelect');
if (baseModelSelect) {
baseModelSelect.addEventListener('change', () => {
const trainMethodLora = document.getElementById('trainMethodLora');
const trainMethodFull = document.getElementById('trainMethodFull');
const paramConfigPanelLora = document.getElementById('paramConfigPanelLora');
const paramConfigPanelFull = document.getElementById('paramConfigPanelFull');
if (baseModelSelect.value) {
// 启用训练方法
trainMethodLora.classList.remove('opacity-50', 'cursor-not-allowed');
trainMethodFull.classList.remove('opacity-50', 'cursor-not-allowed');
trainMethodLora.querySelector('input').disabled = false;
trainMethodFull.querySelector('input').disabled = false;
} else {
// 禁用训练方法
trainMethodLora.classList.add('opacity-50', 'cursor-not-allowed');
trainMethodFull.classList.add('opacity-50', 'cursor-not-allowed');
trainMethodLora.querySelector('input').disabled = true;
trainMethodFull.querySelector('input').disabled = true;
// 隐藏参数配置面板
paramConfigPanelLora.classList.add('hidden');
paramConfigPanelFull.classList.add('hidden');
// 取消选择训练方法
document.querySelectorAll('input[name="train_method"]').forEach(input => input.checked = false);
}
});
}
// 训练方法选择后显示/隐藏对应的参数配置面板
document.querySelectorAll('input[name="train_method"]').forEach(input => {
input.addEventListener('change', () => {
const paramConfigPanelLora = document.getElementById('paramConfigPanelLora');
const paramConfigPanelFull = document.getElementById('paramConfigPanelFull');
if (input.checked && input.value === 'lora') {
paramConfigPanelLora.classList.remove('hidden');
paramConfigPanelFull.classList.add('hidden');
} else if (input.checked && input.value === 'full') {
paramConfigPanelLora.classList.add('hidden');
paramConfigPanelFull.classList.remove('hidden');
} else {
paramConfigPanelLora.classList.add('hidden');
paramConfigPanelFull.classList.add('hidden');
}
});
});
}
// 加载数据集列表
async function loadDatasets() {
try {
const response = await fetch(`${API_BASE}/dataset-manage`);
const result = await response.json();
if (result.code === 0) {
// 训练集下拉框
const trainSelect = document.querySelector('select[name="dataset_id"]');
if (trainSelect) {
trainSelect.innerHTML = '<option value="">请选择</option>' +
result.data.map(d => `<option value="${d.id}">${d.name}</option>`).join('');
}
// 验证集下拉框
const validSelect = document.querySelector('select[name="valid_dataset_id"]');
if (validSelect) {
validSelect.innerHTML = '<option value="">请选择验证数据集</option>' +
result.data.map(d => `<option value="${d.id}">${d.name}</option>`).join('');
}
}
} catch (e) {
console.error('加载数据集失败:', e);
}
}
// 切换验证集面板显示
function toggleValidSetPanel(type) {
const autoPanel = document.getElementById('validAutoPanel');
const customPanel = document.getElementById('validCustomPanel');
if (type === 'auto') {
autoPanel.classList.remove('hidden');
customPanel.classList.add('hidden');
} else {
autoPanel.classList.add('hidden');
customPanel.classList.remove('hidden');
}
}
// 恢复默认配置
function resetDefaultConfig(type) {
if (type === 'lora') {
const defaults = {
'batch_size_lora': '16',
'learning_rate_lora': '3e-4',
'n_epochs_lora': '3',
'eval_steps_lora': '50',
'lora_alpha': '32',
'lora_dropout': '0.1',
'lora_rank': '8',
'lr_scheduler_type_lora': 'linear',
'max_length_lora': '8192',
'warmup_ratio_lora': '0.05',
'weight_decay_lora': '0.01'
};
for (const [name, value] of Object.entries(defaults)) {
const input = document.querySelector(`input[name="${name}"]`);
if (input) input.value = value;
const select = document.querySelector(`select[name="${name}"]`);
if (select) select.value = value;
}
} else {
const defaults = {
'batch_size_full': '16',
'learning_rate_full': '1e-5',
'n_epochs_full': '3',
'eval_steps_full': '50',
'lr_scheduler_type_full': 'linear',
'max_length_full': '8192',
'warmup_ratio_full': '0.05',
'weight_decay_full': '0.01'
};
for (const [name, value] of Object.entries(defaults)) {
const input = document.querySelector(`input[name="${name}"]`);
if (input) input.value = value;
const select = document.querySelector(`select[name="${name}"]`);
if (select) select.value = value;
}
}
}
// 收起/展开参数配置表格
function toggleParamTable(type) {
const container = type === 'lora' ? document.getElementById('paramTableContainerLora') : document.getElementById('paramTableContainerFull');
const btn = type === 'lora' ? document.getElementById('toggleParamBtnLora') : document.getElementById('toggleParamBtnFull');
const icon = btn.querySelector('i');
const text = type === 'lora' ? document.getElementById('toggleParamTextLora') : document.getElementById('toggleParamTextFull');
if (container.classList.contains('hidden')) {
container.classList.remove('hidden');
icon.className = 'fa fa-chevron-up mr-1';
text.textContent = '收起配置';
} else {
container.classList.add('hidden');
icon.className = 'fa fa-chevron-down mr-1';
text.textContent = '展开配置';
}
}
// ============ 自定义消息弹窗 ============
// 显示消息弹窗
function showMessage(title, message, type = 'info', onConfirm) {
const modal = document.getElementById('customModal');
const modalTitle = document.getElementById('modalTitle');
const modalMessage = document.getElementById('modalMessage');
const modalIcon = document.getElementById('modalIcon');
const modalConfirmBtn = document.getElementById('modalConfirmBtn');
const modalConfirmBtn2 = document.getElementById('modalConfirmBtn2');
const modalBtnGroup = document.getElementById('modalBtnGroup');
const modalSingleBtnGroup = document.getElementById('modalSingleBtnGroup');
// 设置标题
modalTitle.textContent = title;
modalTitle.className = 'text-lg font-medium text-gray-800 mb-2';
// 设置消息
modalMessage.innerHTML = message;
// 设置图标和按钮颜色
if (type === 'success') {
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-green-100 flex items-center justify-center"><i class="fa fa-check text-xl text-green-600"></i></div>';
} else if (type === 'error') {
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-red-100 flex items-center justify-center"><i class="fa fa-times text-xl text-red-600"></i></div>';
} else if (type === 'warning') {
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-yellow-100 flex items-center justify-center"><i class="fa fa-exclamation text-xl text-yellow-600"></i></div>';
} else {
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-blue-100 flex items-center justify-center"><i class="fa fa-info text-xl text-blue-600"></i></div>';
}
// 单按钮模式
modalBtnGroup.classList.add('hidden');
modalSingleBtnGroup.classList.remove('hidden');
const confirmBtn = modalConfirmBtn2;
if (type === 'error') {
confirmBtn.className = 'px-6 py-2 bg-danger text-white rounded-lg hover:bg-danger/90 transition-colors';
} else {
confirmBtn.className = 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
}
// 显示弹窗
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
// 绑定确认按钮事件
confirmBtn.onclick = () => {
closeModal();
if (onConfirm) onConfirm();
};
}
// 关闭消息弹窗
function closeModal() {
const modal = document.getElementById('customModal');
modal.classList.add('hidden');
document.body.style.overflow = '';
}
// 确认弹窗(两个按钮)
function showConfirm(title, message, onConfirm, onCancel) {
const modal = document.getElementById('customModal');
const modalTitle = document.getElementById('modalTitle');
const modalMessage = document.getElementById('modalMessage');
const modalIcon = document.getElementById('modalIcon');
const modalConfirmBtn = document.getElementById('modalConfirmBtn');
const modalCancelBtn = document.getElementById('modalCancelBtn');
const modalBtnGroup = document.getElementById('modalBtnGroup');
modalTitle.textContent = title;
modalMessage.innerHTML = message;
modalIcon.innerHTML = '<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-blue-100 flex items-center justify-center"><i class="fa fa-question text-xl text-blue-600"></i></div>';
modalBtnGroup.classList.remove('hidden');
modalConfirmBtn.textContent = '确定';
modalConfirmBtn.className = 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
modalConfirmBtn.onclick = () => {
closeModal();
if (onConfirm) onConfirm();
};
modalCancelBtn.onclick = () => {
closeModal();
if (onCancel) onCancel();
};
}
// 提交创建表单
async function submitCreateForm() {
const form = document.getElementById('createForm');
const formData = new FormData(form);
const data = {
name: formData.get('name'),
base_model: formData.get('base_model'),
train_type: formData.get('train_type'),
train_method: formData.get('train_method'),
dataset_id: formData.get('dataset_id'),
valid_split: formData.get('valid_split'),
valid_ratio: parseInt(formData.get('valid_ratio')) || 10,
output_model_name: formData.get('output_model_name'),
status: 'pending',
progress: 0
};
if (!data.name) {
showMessage('提示', '请输入任务名称', 'warning');
return;
}
if (!data.base_model) {
showMessage('提示', '请选择基础模型', 'warning');
return;
}
if (!data.dataset_id) {
showMessage('提示', '请选择训练集', 'warning');
return;
}
try {
const response = await fetch(`${API_BASE}/fine-tune`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.code === 0) {
showMessage('成功', '训练任务创建成功!', 'success', () => {
goBack();
});
} else {
showMessage('错误', result.message || '创建失败', 'error');
}
} catch (error) {
showMessage('错误', '创建失败: ' + error.message, 'error');
}
}
</script>
<!-- 自定义消息弹窗 -->
<div id="customModal" class="hidden fixed inset-0 bg-black/50 z-50 flex items-center justify-center" onclick="if(event.target === this) closeModal();">
<div class="bg-white rounded-xl shadow-xl max-w-md w-full mx-4 overflow-hidden transform transition-all">
<div class="p-6 text-center">
<div id="modalIcon"></div>
<h3 id="modalTitle" class="text-lg font-medium text-gray-800 mb-2"></h3>
<p id="modalMessage" class="text-gray-600 text-sm"></p>
</div>
<div id="modalBtnGroup" class="hidden px-6 pb-6 flex justify-center space-x-3">
<button id="modalCancelBtn" class="px-6 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors">
取消
</button>
<button id="modalConfirmBtn" class="px-6 py-2 text-white rounded-lg transition-colors">
确定
</button>
</div>
<div id="modalSingleBtnGroup" class="px-6 pb-6 flex justify-center">
<button id="modalConfirmBtn2" class="px-6 py-2 text-white rounded-lg transition-colors">
确定
</button>
</div>
</div>
</div>
</body>
</html>