1152 lines
72 KiB
HTML
1152 lines
72 KiB
HTML
<!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 sidebar-item-active">
|
||
<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="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="permission" class="nav-link flex items-center px-4 py-2.5 hover:bg-sidebarBg/20 transition-colors">
|
||
<i class="fa fa-user-circle-o w-5 text-center"></i>
|
||
<span class="ml-2">权限管理</span>
|
||
</a>
|
||
<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-cog 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']
|
||
},
|
||
'permission': {
|
||
title: '权限管理',
|
||
api: 'permission',
|
||
hasCreate: true,
|
||
createText: '添加用户',
|
||
columns: [
|
||
{ title: '用户名', key: 'username' },
|
||
{ title: '角色', key: 'role' },
|
||
{ title: '权限', key: 'permissions', 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() {
|
||
// 加载默认页面
|
||
loadPage('fine-tune');
|
||
|
||
// 绑定导航点击事件
|
||
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) {
|
||
if (!confirm('确定要删除这条记录吗?')) return;
|
||
|
||
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 {
|
||
alert(result.message || '删除失败');
|
||
}
|
||
} catch (error) {
|
||
alert('删除失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 渲染表格页面
|
||
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})` : `alert('${actionLabels[action] || action}功能开发中...')`}"
|
||
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() {
|
||
alert('配置保存功能开发中...');
|
||
}
|
||
|
||
// 当前页面状态
|
||
let currentPage = 'fine-tune';
|
||
let currentParentPage = null;
|
||
|
||
// 显示创建表单页面
|
||
function showCreateModal(apiType) {
|
||
const container = document.getElementById('page-content');
|
||
|
||
if (apiType === 'fine-tune') {
|
||
currentParentPage = currentPage;
|
||
currentPage = 'fine-tune-create';
|
||
container.innerHTML = renderFineTuneCreatePage();
|
||
bindCreatePageEvents();
|
||
loadDatasets();
|
||
} else {
|
||
alert('该功能开发中...');
|
||
}
|
||
}
|
||
|
||
// 返回列表页
|
||
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 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 = '展开配置';
|
||
}
|
||
}
|
||
|
||
// 提交创建表单
|
||
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) {
|
||
alert('请输入任务名称');
|
||
return;
|
||
}
|
||
if (!data.base_model) {
|
||
alert('请选择基础模型');
|
||
return;
|
||
}
|
||
if (!data.dataset_id) {
|
||
alert('请选择训练集');
|
||
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) {
|
||
alert('创建成功!');
|
||
goBack();
|
||
} else {
|
||
alert(result.message || '创建失败');
|
||
}
|
||
} catch (error) {
|
||
alert('创建失败: ' + error.message);
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|