模型微调已经调通
增加了参数预览
This commit is contained in:
@@ -293,6 +293,92 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 选择训练模板 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm text-gray-600 mb-3">
|
||||
<span class="text-red-500 mr-1">*</span>训练模板
|
||||
</label>
|
||||
<select name="template" id="templateSelect" class="form-select flex-1 max-w-md">
|
||||
<optgroup label="Qwen 系列">
|
||||
<option value="qwen">qwen (Qwen/Qwen2)</option>
|
||||
<option value="qwen3">qwen3 (Qwen3)</option>
|
||||
<option value="qwen3_nothink">qwen3_nothink (Qwen3-Thinking)</option>
|
||||
<option value="qwen2_vl">qwen2_vl (Qwen2-VL)</option>
|
||||
<option value="qwen3_vl">qwen3_vl (Qwen3-VL)</option>
|
||||
<option value="qwen2_audio">qwen2_audio (Qwen2-Audio)</option>
|
||||
<option value="qwen2_omni">qwen2_omni (Qwen2.5-Omni)</option>
|
||||
<option value="qwen3_omni">qwen3_omni (Qwen3-Omni)</option>
|
||||
</optgroup>
|
||||
<optgroup label="LLaMA 系列">
|
||||
<option value="llama">llama (LLaMA)</option>
|
||||
<option value="llama2">llama2 (LLaMA 2)</option>
|
||||
<option value="llama3">llama3 (LLaMA 3/3.3)</option>
|
||||
<option value="llama4">llama4 (LLaMA 4)</option>
|
||||
<option value="mllama">mllama (LLaMA 3.2 Vision)</option>
|
||||
<option value="llava">llava (LLaVA-1.5)</option>
|
||||
<option value="llava_next">llava_next (LLaVA-NeXT)</option>
|
||||
<option value="llava_next_video">llava_next_video (LLaVA-NeXT-Video)</option>
|
||||
</optgroup>
|
||||
<optgroup label="DeepSeek 系列">
|
||||
<option value="deepseek">deepseek (DeepSeek LLM/Code/MoE)</option>
|
||||
<option value="deepseek3">deepseek3 (DeepSeek 3-3.2)</option>
|
||||
<option value="deepseekr1">deepseekr1 (DeepSeek R1 Distill)</option>
|
||||
</optgroup>
|
||||
<optgroup label="GLM 系列">
|
||||
<option value="glm4">glm4 (GLM-4/GLM-4-0414/GLM-Z1)</option>
|
||||
<option value="glm4_moe">glm4_moe (GLM-4.5)</option>
|
||||
<option value="glm4_5v">glm4_5v (GLM-4.5V)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Gemma 系列">
|
||||
<option value="gemma">gemma (Gemma/Gemma 2/CodeGemma)</option>
|
||||
<option value="gemma2">gemma2 (Gemma 2)</option>
|
||||
<option value="gemma3">gemma3 (Gemma 3)</option>
|
||||
<option value="gemma3n">gemma3n (Gemma 3n)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Phi 系列">
|
||||
<option value="phi">phi (Phi-3/Phi-3.5)</option>
|
||||
<option value="phi_small">phi_small (Phi-3-small)</option>
|
||||
<option value="phi4_mini">phi4_mini (Phi-4-mini)</option>
|
||||
<option value="phi4">phi4 (Phi-4)</option>
|
||||
</optgroup>
|
||||
<optgroup label="InternLM 系列">
|
||||
<option value="intern2">intern2 (InternLM 2-3)</option>
|
||||
<option value="intern_vl">intern_vl (InternVL 2.5-3.5)</option>
|
||||
<option value="intern_s1">intern_s1 (Intern-S1-mini)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Mistral 系列">
|
||||
<option value="mistral">mistral (Mistral/Mixtral)</option>
|
||||
<option value="ministral3">ministral3 (Ministral 3)</option>
|
||||
</optgroup>
|
||||
<optgroup label="其他系列">
|
||||
<option value="yi">yi (Yi)</option>
|
||||
<option value="baichuan">baichuan (Baichuan)</option>
|
||||
<option value="falcon">falcon (Falcon)</option>
|
||||
<option value="falcon_h1">falcon_h1 (Falcon H1)</option>
|
||||
<option value="pixtral">pixtral (Pixtral)</option>
|
||||
<option value="paligemma">paligemma (PaliGemma)</option>
|
||||
<option value="minicpm_o">minicpm_o (MiniCPM-o-2.6)</option>
|
||||
<option value="minicpm_v">minicpm_v (MiniCPM-V-2.6)</option>
|
||||
<option value="seed_oss">seed_oss (Seed OSS)</option>
|
||||
<option value="seed_coder">seed_coder (Seed Coder)</option>
|
||||
<option value="kimi_vl">kimi_vl (Kimi-VL)</option>
|
||||
<option value="hunyuan">hunyuan (Hunyuan)</option>
|
||||
<option value="hunyuan_small">hunyuan_small (Hunyuan1.5)</option>
|
||||
<option value="granite3">granite3 (Granite 3)</option>
|
||||
<option value="granite4">granite4 (Granite 3-4)</option>
|
||||
<option value="mimo">mimo (MiMo)</option>
|
||||
<option value="mimo_v2">mimo_v2 (MiMo V2)</option>
|
||||
<option value="lfm2">lfm2 (LFM 2.5)</option>
|
||||
<option value="lfm2_vl">lfm2_vl (LFM 2.5 VL)</option>
|
||||
<option value="bailing_v2">bailing_v2 (Ling 2.0)</option>
|
||||
<option value="yuan">yuan (Yuan 2)</option>
|
||||
<option value="ernie_nothink">ernie_nothink (ERNIE-4.5)</option>
|
||||
<option value="gpt_oss">gpt_oss (GPT-OSS)</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<p class="text-xs text-gray-400 mt-1">选择与您的模型匹配的对话模板,确保训练数据格式正确</p>
|
||||
</div>
|
||||
|
||||
<!-- 训练方法 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm text-gray-600 mb-3">训练方法</label>
|
||||
@@ -512,7 +598,7 @@
|
||||
<span class="text-red-500 mr-1">*</span>训练集
|
||||
</label>
|
||||
<div class="flex items-center">
|
||||
<select name="dataset_id" id="trainDatasetSelect" class="form-select flex-1 max-w-md">
|
||||
<select name="train_dataset_id" id="trainDatasetSelect" class="form-select flex-1 max-w-md">
|
||||
<option value="">请选择训练数据集</option>
|
||||
</select>
|
||||
<button type="button" class="ml-2 text-primary text-sm flex items-center hover:text-primary/80" onclick="loadDatasets()">
|
||||
@@ -524,38 +610,6 @@
|
||||
</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="toggleValidSplit()">
|
||||
<span class="text-sm">自动切分</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="valid_split" value="custom" class="mr-2" onchange="toggleValidSplit()">
|
||||
<span class="text-sm">选择数据集</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="autoSplitSection" 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="customSplitSection" class="hidden">
|
||||
<div class="flex items-center">
|
||||
<select name="valid_dataset_id" id="validDatasetSelect" class="form-select flex-1 max-w-md">
|
||||
<option value="">请选择验证数据集</option>
|
||||
</select>
|
||||
<button type="button" class="ml-2 text-primary text-sm flex items-center hover:text-primary/80" onclick="loadDatasets()">
|
||||
<i class="fa fa-refresh"></i>
|
||||
</button>
|
||||
<button type="button" class="ml-3 bg-white border border-primary text-primary rounded px-3 py-1.5 text-sm hover:bg-primary/5" onclick="window.location.href='dataset-create.html?from=fine-tune'">
|
||||
+ 新增数据集
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 训练产出 -->
|
||||
@@ -571,14 +625,19 @@
|
||||
</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 class="mt-4">
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="text-sm font-medium text-gray-600">训练命令预览</span>
|
||||
<button type="button" onclick="updateCommandPreview()" class="ml-2 px-2 py-0.5 bg-blue-50 text-blue-600 text-xs rounded hover:bg-blue-100">
|
||||
<i class="fa fa-refresh mr-1"></i>刷新
|
||||
</button>
|
||||
</div>
|
||||
<div class="bg-gray-900 rounded-lg p-3 overflow-x-auto">
|
||||
<pre id="commandPreview" class="text-xs text-green-400 font-mono whitespace-pre-wrap break-all">请选择完整配置后查看预览命令</pre>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">为保障您的数据安全,平台会为导出的模型文件开启 OSS 服务端加密</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
@@ -591,11 +650,6 @@
|
||||
取消
|
||||
</a>
|
||||
</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>
|
||||
@@ -648,6 +702,9 @@
|
||||
// 加载GPU列表
|
||||
loadGPUList();
|
||||
|
||||
// 初始化训练命令预览
|
||||
initCommandPreview();
|
||||
|
||||
// 设置侧边栏当前页高亮
|
||||
const currentPage = 'fine-tune';
|
||||
document.querySelectorAll('.nav-link').forEach(link => {
|
||||
@@ -683,20 +740,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 切换验证集切分方式
|
||||
function toggleValidSplit() {
|
||||
const validSplit = document.querySelector('input[name="valid_split"]:checked').value;
|
||||
const autoSection = document.getElementById('autoSplitSection');
|
||||
const customSection = document.getElementById('customSplitSection');
|
||||
if (validSplit === 'auto') {
|
||||
autoSection.classList.remove('hidden');
|
||||
customSection.classList.add('hidden');
|
||||
} else {
|
||||
autoSection.classList.add('hidden');
|
||||
customSection.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 切换训练方法 - 显示/隐藏LoRA参数
|
||||
function toggleTrainMethod() {
|
||||
const trainMethod = document.querySelector('input[name="train_method"]:checked').value;
|
||||
@@ -782,12 +825,6 @@
|
||||
trainSelect.innerHTML = '<option value="">请选择训练数据集</option>' +
|
||||
result.data.map(d => `<option value="${d.id}">${d.name}</option>`).join('');
|
||||
}
|
||||
// 更新验证集下拉框
|
||||
const validSelect = document.getElementById('validDatasetSelect');
|
||||
if (validSelect) {
|
||||
validSelect.innerHTML = '<option value="">请选择验证数据集</option>' +
|
||||
result.data.map(d => `<option value="${d.id}">${d.name}</option>`).join('');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载数据集失败:', e);
|
||||
@@ -968,22 +1005,35 @@
|
||||
async function submitForm() {
|
||||
const form = document.getElementById('createForm');
|
||||
const formData = new FormData(form);
|
||||
const validSplit = formData.get('valid_split');
|
||||
|
||||
// 获取选中的GPU
|
||||
const selectedGPUs = getSelectedGPUs();
|
||||
|
||||
// 收集训练参数
|
||||
const trainParams = {
|
||||
batch_size: parseInt(formData.get('batch_size')) || 1,
|
||||
learning_rate: parseFloat(formData.get('learning_rate')) || 0.0001,
|
||||
n_epochs: parseFloat(formData.get('n_epochs')) || 1.0,
|
||||
eval_steps: parseInt(formData.get('eval_steps')) || 100,
|
||||
lr_scheduler_type: formData.get('lr_scheduler_type') || 'cosine',
|
||||
max_length: parseInt(formData.get('max_length')) || 512,
|
||||
warmup_ratio: parseFloat(formData.get('warmup_ratio')) || 0.05,
|
||||
weight_decay: parseFloat(formData.get('weight_decay')) || 0.01,
|
||||
lora_alpha: formData.get('lora_alpha') || '32',
|
||||
lora_dropout: parseFloat(formData.get('lora_dropout')) || 0.1,
|
||||
lora_rank: formData.get('lora_rank') || '8'
|
||||
};
|
||||
|
||||
const data = {
|
||||
name: formData.get('name'),
|
||||
base_model: formData.get('base_model'),
|
||||
template: formData.get('template'),
|
||||
train_type: formData.get('train_type'),
|
||||
train_method: formData.get('train_method'),
|
||||
gpus: selectedGPUs, // 添加GPU选择
|
||||
gpus: selectedGPUs,
|
||||
train_dataset_id: formData.get('train_dataset_id'),
|
||||
valid_split: validSplit,
|
||||
valid_ratio: parseInt(formData.get('valid_ratio')) || 10,
|
||||
valid_dataset_id: validSplit === 'custom' ? formData.get('valid_dataset_id') : null,
|
||||
output_model_name: formData.get('output_model_name'),
|
||||
...trainParams,
|
||||
status: 'pending',
|
||||
progress: 0
|
||||
};
|
||||
@@ -1000,33 +1050,201 @@
|
||||
showMessage('提示', '请选择基础模型', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!data.template) {
|
||||
showMessage('提示', '请选择训练模板', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!data.train_dataset_id) {
|
||||
showMessage('提示', '请选择训练集', 'warning');
|
||||
return;
|
||||
}
|
||||
if (validSplit === 'custom' && !data.valid_dataset_id) {
|
||||
showMessage('提示', '请选择验证集', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/fine-tune`, {
|
||||
// 第一步:创建训练任务记录
|
||||
const createResponse = 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', () => {
|
||||
const createResult = await createResponse.json();
|
||||
if (createResult.code !== 0) {
|
||||
showMessage('错误', createResult.message || '创建任务失败', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const taskId = createResult.id;
|
||||
|
||||
// 第二步:启动训练
|
||||
const startData = {
|
||||
task_id: taskId,
|
||||
base_model: data.base_model,
|
||||
template: data.template,
|
||||
train_type: data.train_type,
|
||||
train_method: data.train_method,
|
||||
train_dataset_id: data.train_dataset_id,
|
||||
output_model_name: data.output_model_name,
|
||||
...trainParams
|
||||
};
|
||||
|
||||
const startResponse = await fetch(`${API_BASE}/fine-tune/start`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(startData)
|
||||
});
|
||||
const startResult = await startResponse.json();
|
||||
|
||||
if (startResult.code === 0) {
|
||||
const cmd = startResult.data?.command || '';
|
||||
showMessage('成功', `训练任务已启动!<br><br><code class="text-xs bg-gray-100 p-1 rounded">${cmd}</code>`, 'success', () => {
|
||||
window.location.href = 'main.html';
|
||||
});
|
||||
} else {
|
||||
showMessage('错误', result.message || '创建失败', 'error');
|
||||
// 更新任务状态为失败
|
||||
await fetch(`${API_BASE}/fine-tune/${taskId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ status: 'failed' })
|
||||
});
|
||||
showMessage('错误', startResult.message || '启动训练失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage('错误', '创建失败: ' + error.message, 'error');
|
||||
showMessage('错误', '操作失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 生成训练命令预览
|
||||
function buildCommandPreview() {
|
||||
const form = document.getElementById('createForm');
|
||||
const formData = new FormData(form);
|
||||
|
||||
// 获取选中的GPU
|
||||
const selectedGPUs = getSelectedGPUs();
|
||||
let gpuIds = '0';
|
||||
if (selectedGPUs.length > 0) {
|
||||
gpuIds = selectedGPUs.map(g => g.id.replace('gpu', '')).filter(g => /^\d+$/.test(g)).join(',');
|
||||
}
|
||||
|
||||
// 获取模型路径
|
||||
const baseModelSelect = form.querySelector('select[name="base_model"]');
|
||||
let modelPath = formData.get('base_model') || '';
|
||||
if (baseModelSelect && baseModelSelect.selectedOptions.length > 0) {
|
||||
const selectedOption = baseModelSelect.selectedOptions[0];
|
||||
const pathValue = selectedOption.getAttribute('data-path');
|
||||
if (pathValue) {
|
||||
modelPath = pathValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取模板
|
||||
const template = formData.get('template') || 'qwen3';
|
||||
|
||||
// 获取训练类型
|
||||
const trainType = formData.get('train_type') || 'SFT';
|
||||
const stageMap = { 'SFT': 'sft', 'DPO': 'dpo', 'CPT': 'cpt' };
|
||||
|
||||
// 获取训练方法
|
||||
const trainMethod = formData.get('train_method') || 'lora';
|
||||
const methodMap = { 'lora': 'lora', 'full': 'full' };
|
||||
|
||||
// 获取输出模型名称
|
||||
const outputModelName = formData.get('output_model_name') || `${template}/${trainMethod}`;
|
||||
const outputDir = outputModelName.startsWith('./') ? outputModelName : `./saves/${outputModelName}`;
|
||||
|
||||
// 获取数据集名称
|
||||
const trainDatasetSelect = form.querySelector('select[name="train_dataset_id"]');
|
||||
let datasetName = formData.get('train_dataset_id') || 'dataset_name';
|
||||
if (trainDatasetSelect && trainDatasetSelect.selectedOptions.length > 0) {
|
||||
const selectedOption = trainDatasetSelect.selectedOptions[0];
|
||||
const datasetValue = selectedOption.getAttribute('data-name');
|
||||
if (datasetValue) {
|
||||
datasetName = datasetValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取训练参数
|
||||
const batchSize = parseInt(formData.get('batch_size')) || 1;
|
||||
const learningRate = parseFloat(formData.get('learning_rate')) || 0.0001;
|
||||
const nEpochs = parseFloat(formData.get('n_epochs')) || 1.0;
|
||||
const maxLength = parseInt(formData.get('max_length')) || 512;
|
||||
const warmupSteps = parseInt(formData.get('warmup_steps')) || 20;
|
||||
const evalSteps = parseInt(formData.get('eval_steps')) || 100;
|
||||
const gradientAccumulationSteps = parseInt(formData.get('gradient_accumulation_steps')) || 8;
|
||||
const lrSchedulerType = formData.get('lr_scheduler_type') || 'cosine';
|
||||
|
||||
// LoRA参数
|
||||
const loraAlpha = formData.get('lora_alpha') || '32';
|
||||
const loraDropout = parseFloat(formData.get('lora_dropout')) || 0.1;
|
||||
const loraRank = formData.get('lora_rank') || '8';
|
||||
|
||||
// 构建命令
|
||||
let cmd = `CUDA_VISIBLE_DEVICES=${gpuIds} llamafactory-cli train \\\n`;
|
||||
cmd += ` --stage ${stageMap[trainType] || 'sft'} \\\n`;
|
||||
cmd += ` --do_train \\\n`;
|
||||
cmd += ` --model_name_or_path ${modelPath} \\\n`;
|
||||
cmd += ` --dataset ${datasetName} \\\n`;
|
||||
cmd += ` --dataset_dir ./datasets \\\n`;
|
||||
cmd += ` --template ${template} \\\n`;
|
||||
cmd += ` --finetuning_type ${methodMap[trainMethod] || 'lora'} \\\n`;
|
||||
|
||||
// LoRA参数(仅lora方法时显示)
|
||||
if (trainMethod === 'lora') {
|
||||
cmd += ` --lora_alpha ${loraAlpha} \\\n`;
|
||||
cmd += ` --lora_dropout ${loraDropout} \\\n`;
|
||||
cmd += ` --lora_rank ${loraRank} \\\n`;
|
||||
}
|
||||
|
||||
cmd += ` --output_dir ${outputDir} \\\n`;
|
||||
cmd += ` --overwrite_cache \\\n`;
|
||||
cmd += ` --overwrite_output_dir \\\n`;
|
||||
cmd += ` --cutoff_len ${maxLength} \\\n`;
|
||||
cmd += ` --preprocessing_num_workers 16 \\\n`;
|
||||
cmd += ` --per_device_train_batch_size ${batchSize} \\\n`;
|
||||
cmd += ` --per_device_eval_batch_size 1 \\\n`;
|
||||
cmd += ` --gradient_accumulation_steps ${gradientAccumulationSteps} \\\n`;
|
||||
cmd += ` --lr_scheduler_type ${lrSchedulerType} \\\n`;
|
||||
cmd += ` --logging_steps 50 \\\n`;
|
||||
cmd += ` --warmup_steps ${warmupSteps} \\\n`;
|
||||
cmd += ` --save_steps 100 \\\n`;
|
||||
cmd += ` --eval_steps ${evalSteps} \\\n`;
|
||||
cmd += ` --learning_rate ${learningRate} \\\n`;
|
||||
cmd += ` --num_train_epochs ${nEpochs}`;
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
// 更新命令预览
|
||||
function updateCommandPreview() {
|
||||
const preview = document.getElementById('commandPreview');
|
||||
const cmd = buildCommandPreview();
|
||||
preview.textContent = cmd;
|
||||
}
|
||||
|
||||
// 监听表单变化自动更新预览
|
||||
function initCommandPreview() {
|
||||
const form = document.getElementById('createForm');
|
||||
|
||||
// 监听所有 input 和 select 的变化
|
||||
const inputs = form.querySelectorAll('input, select');
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('change', () => setTimeout(updateCommandPreview, 100));
|
||||
if (input.type === 'text' || input.type === 'number') {
|
||||
input.addEventListener('input', () => setTimeout(updateCommandPreview, 100));
|
||||
}
|
||||
});
|
||||
|
||||
// 监听卡片式单选框的点击事件 (训练类型、训练方法)
|
||||
document.querySelectorAll('.card-radio').forEach(card => {
|
||||
card.addEventListener('click', () => setTimeout(updateCommandPreview, 100));
|
||||
});
|
||||
|
||||
// 监听 GPU 卡片的点击事件
|
||||
document.querySelectorAll('.gpu-card').forEach(card => {
|
||||
card.addEventListener('click', () => setTimeout(updateCommandPreview, 100));
|
||||
});
|
||||
|
||||
// 初始化时更新一次
|
||||
setTimeout(updateCommandPreview, 500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 自定义消息弹窗 -->
|
||||
|
||||
@@ -417,9 +417,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时获取监控数据,并每5秒刷新
|
||||
// 页面加载时获取监控数据,并每30秒刷新
|
||||
fetchSystemMetrics();
|
||||
setInterval(fetchSystemMetrics, 5000);
|
||||
setInterval(fetchSystemMetrics, 30000);
|
||||
|
||||
// 各功能模块的表格配置
|
||||
const tableConfigs = {
|
||||
|
||||
@@ -517,7 +517,7 @@
|
||||
if (select.options.length > 1) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/local-models`);
|
||||
const response = await fetch(`${API_BASE}/model-manage/local-models`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 0 && result.data && result.data.models) {
|
||||
|
||||
Reference in New Issue
Block a user