Files
YG_FT_Platform/web/pages/model-compare-create.html

770 lines
38 KiB
HTML
Raw Normal View History

2026-01-20 21:46:18 +08:00
<!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="../lib/tailwindcss/tailwind.js"></script>
<script>
if (typeof console !== 'undefined' && console.warn) {
const originalWarn = console.warn;
console.warn = function(...args) {
if (args[0] && args[0].includes && args[0].includes('cdn.tailwindcss.com')) {
return;
}
originalWarn.apply(console, args);
};
}
</script>
<script>
// 设置当前页面,供侧边栏高亮使用
window.sidebarCurrentPage = 'model-compare';
</script>
2026-01-20 21:46:18 +08:00
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<!-- 侧边栏加载器 -->
<script src="../js/components/sidebar-loader.js"></script>
2026-01-20 21:46:18 +08:00
<style>
.model-card {
transition: all 0.2s;
}
.model-card:hover {
border-color: #1890ff;
}
.model-card.selected {
border-color: #1890ff;
background-color: rgba(24, 144, 255, 0.05);
}
.model-card.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.form-input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.875rem;
transition: border-color 0.2s, outline 0.2s;
}
.form-input:focus {
border-color: #1890ff;
outline: none;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.form-select {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.875rem;
background-color: white;
transition: border-color 0.2s, outline 0.2s;
}
.form-select:focus {
border-color: #1890ff;
outline: none;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
2026-01-20 21:46:18 +08:00
.form-label {
display: block;
font-size: 0.875rem;
font-weight: 500;
color: #374151;
margin-bottom: 0.25rem;
}
.bg-primary { background-color: #1890ff; }
.text-primary { color: #1890ff; }
.border-primary { border-color: #1890ff; }
:root { --primary: #1890ff; --danger: #f5222d; --success: #52c41a; }
</style>
</head>
<body class="antialiased bg-gray-50 flex h-screen overflow-hidden">
<!-- 侧边栏容器 -->
<div id="sidebar-container"></div>
2026-01-20 21:46:18 +08:00
<div class="flex-1 flex flex-col overflow-hidden">
<!-- 顶部导航 -->
<header class="bg-white 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-4">
<a href="#" onclick="goBack()" class="text-gray-500 hover:text-gray-700 flex items-center">
<i class="fa fa-arrow-left"></i>
<span class="ml-1">上一步</span>
</a>
</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">
2026-01-21 15:33:43 +08:00
<!-- 页面标题 -->
<div class="bg-white rounded-lg shadow-sm w-full p-4 border-b border-gray-100 mb-4">
<div class="flex items-center text-sm">
<a href="main.html?page=model-compare" class="text-primary hover:underline">模型对比</a>
<span class="mx-2 text-gray-300">/</span>
<span class="text-gray-800 font-medium">新建对比</span>
2026-01-20 21:46:18 +08:00
</div>
2026-01-21 15:33:43 +08:00
</div>
<!-- 表单内容 -->
<div class="bg-white rounded-lg shadow-sm w-full">
2026-01-20 21:46:18 +08:00
<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="space-y-4">
<div>
<label class="form-label">对比名称 <span class="text-red-500">*</span></label>
<input type="text" name="name" class="form-input" placeholder="请输入对比任务名称" maxlength="50">
<p class="text-xs text-gray-400 mt-1"><span id="nameCount">0</span> / 50</p>
</div>
<div>
<label class="form-label">描述</label>
<textarea name="description" class="form-input" rows="3" placeholder="请输入对比任务描述(可选)" maxlength="200"></textarea>
<p class="text-xs text-gray-400 mt-1"><span id="descCount">0</span> / 200</p>
2026-01-20 21:46:18 +08:00
</div>
</div>
</div>
2026-01-20 21:46:18 +08:00
<!-- 选择对比模型 -->
<div class="mb-6">
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">选择对比模型 <span class="text-red-500">*</span></h3>
<div class="mb-3 flex items-center justify-between">
<div class="flex items-center space-x-3">
<span class="text-sm text-gray-600">已选择 <span id="selectedCount" class="text-primary font-medium">0</span> / 2 个模型</span>
<span class="text-xs text-gray-400">最多选择2个模型进行对比</span>
2026-01-20 21:46:18 +08:00
</div>
<div class="relative">
<input type="text" id="modelSearchInput" placeholder="搜索模型..."
class="pl-8 pr-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none w-48">
<i class="fa fa-search absolute left-2.5 top-1/2 transform -translate-y-1/2 text-gray-400 text-xs"></i>
2026-01-20 21:46:18 +08:00
</div>
</div>
<!-- 模型列表容器(带滚动) -->
<div id="modelList" class="border border-gray-200 rounded-lg max-h-80 overflow-y-auto">
<div class="p-8 text-center text-gray-400">
<i class="fa fa-spinner fa-spin text-2xl mb-2"></i>
<p>加载模型列表中...</p>
2026-01-20 21:46:18 +08:00
</div>
</div>
</div>
<!-- 已选模型配置区域 -->
<div id="selectedModelsConfig" class="mb-6" style="display: none;">
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">模型配置</h3>
<div id="configContainer" class="space-y-4">
<!-- 动态生成模型配置卡片 -->
</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="submitForm()" class="px-4 py-2 bg-primary text-white rounded-lg text-sm hover:bg-primary/90 transition-colors">
开始对比
</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 transition-colors">
取消
</button>
</div>
</div>
</form>
2026-01-20 21:46:18 +08:00
</div>
</div>
2026-01-20 21:46:18 +08:00
</main>
</div>
<!-- 自定义消息弹窗 -->
<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-sm w-full mx-4 overflow-hidden transform transition-all">
<div class="flex flex-col items-center justify-center min-h-[160px] py-6">
<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="modalSingleBtnGroup" class="px-6 pb-6 flex justify-center">
<button id="modalConfirmBtn2" class="px-6 py-2 w-full text-white rounded transition-colors text-sm max-w-[160px]">
确定
</button>
</div>
</div>
</div>
<script>
// 使用 IIFE 避免全局变量污染
(function() {
// API 基础地址 - 优先使用 main.html 中定义的全局变量
const getApiBase = () => {
const protocol = window.location.protocol;
const hostname = window.location.hostname;
return `${protocol}//${hostname}:7861/api`;
};
const API_BASE = typeof window.API_BASE !== 'undefined' ? window.API_BASE : getApiBase();
2026-01-20 21:46:18 +08:00
let allModels = [];
let selectedModels = new Map(); // 使用 Map 存储已选模型及其配置
let availableGPUs = []; // 可用GPU列表
let usedPorts = new Set(); // 已使用的端口
2026-01-20 21:46:18 +08:00
// 返回列表页
function goBack() {
window.location.href = 'main.html?page=model-compare';
}
2026-01-20 21:46:18 +08:00
// 加载模型列表
async function loadModels() {
try {
// 并行加载数据库模型、已训练模型和GPU信息
const [dbResponse, trainedResponse, gpuResponse] = await Promise.all([
fetch(`${API_BASE}/model-manage`),
fetch(`${API_BASE}/model-manage/trained-models`),
fetch(`${API_BASE}/system-info`)
]);
// 处理GPU信息
const gpuResult = await gpuResponse.json();
if (gpuResult.code === 0 && gpuResult.data.gpu) {
availableGPUs = gpuResult.data.gpu.map((gpu, index) => ({
id: index,
name: gpu.name || `GPU ${index}`,
memory: `${gpu.memory_used_gb || 0}/${gpu.memory_total_gb || 0} GB`,
utilization: gpu.gpu_percent || 0
}));
} else {
availableGPUs = [{ id: 0, name: 'GPU 0', memory: 'N/A', utilization: 0 }];
}
2026-01-29 17:34:59 +08:00
const dbResult = await dbResponse.json();
const trainedResult = await trainedResponse.json();
2026-01-29 17:34:59 +08:00
let dbModels = [];
let trainedModels = [];
2026-01-29 17:34:59 +08:00
// 数据库模型
if (dbResult.code === 0) {
dbModels = dbResult.data || [];
}
2026-01-29 17:34:59 +08:00
// 已训练模型
if (trainedResult.code === 0) {
const trainedData = trainedResult.data?.models || [];
trainedData.forEach(model => {
if (model.train_methods && model.train_methods.length > 0) {
model.train_methods.forEach(method => {
trainedModels.push({
id: `trained_${model.name}_${method.name}`.replace(/[^a-zA-Z0-9]/g, '_'),
name: `${model.name} (${method.name})`,
type: 'LLM',
source: 'trained',
model_path: model.path,
train_method: method.name,
merged: model.merged, // 是否已合并
merging: model.merging, // 是否正在合并
description: `已训练模型 (${method.name})`
});
2026-01-29 17:34:59 +08:00
});
}
});
}
2026-01-29 17:34:59 +08:00
// 合并所有模型
allModels = [...dbModels, ...trainedModels];
console.log('[DEBUG] 数据库模型:', dbModels.length, '已训练模型:', trainedModels.length);
renderModels();
} catch (error) {
console.error('加载模型失败:', error);
document.getElementById('modelList').innerHTML = `
<div class="p-8 text-center text-gray-400">
<i class="fa fa-exclamation-circle text-2xl mb-2 text-red-400"></i>
<p>加载模型失败,请检查后端服务</p>
</div>
`;
}
2026-01-20 21:46:18 +08:00
}
// 渲染模型列表 - 使用分栏显示数据库模型和已训练模型
function renderModels() {
const container = document.getElementById('modelList');
2026-01-29 17:34:59 +08:00
// 分离数据库模型和已训练模型
const dbModels = allModels.filter(m => m.source !== 'trained');
const trainedModels = allModels.filter(m => m.source === 'trained');
2026-01-29 17:34:59 +08:00
if (allModels.length === 0) {
container.innerHTML = `
<div class="p-8 text-center text-gray-400">
<i class="fa fa-inbox text-2xl mb-2"></i>
<p>暂无模型,请先添加模型</p>
2026-01-20 21:46:18 +08:00
</div>
`;
return;
}
2026-01-20 21:46:18 +08:00
let html = '';
2026-01-29 17:34:59 +08:00
// 数据库模型区域
if (dbModels.length > 0) {
html += `
<div class="border-b border-gray-200">
<div class="px-4 py-2 bg-gray-50 text-xs font-medium text-gray-600 flex items-center">
<i class="fa fa-database mr-1"></i> 数据库模型 (${dbModels.length})
</div>
<div class="divide-y divide-gray-100">
`;
html += dbModels.map(model => renderModelCard(model)).join('');
html += '</div>';
}
2026-01-20 21:46:18 +08:00
// 已训练模型区域
if (trainedModels.length > 0) {
// 统计已合并和未合并的数量
const mergedCount = trainedModels.filter(m => m.merged).length;
html += `
<div class="border-b border-gray-200">
<div class="px-4 py-2 bg-green-50 text-xs font-medium text-green-700 flex items-center justify-between">
<div class="flex items-center">
<i class="fa fa-check-circle mr-1"></i> 已训练模型 (${trainedModels.length})
</div>
<span class="text-orange-600">${mergedCount > 0 ? `${mergedCount}个已合并可选择` : '需合并后才可选择'}</span>
2026-01-29 17:34:59 +08:00
</div>
<div class="divide-y divide-gray-100">
`;
html += trainedModels.map(model => renderModelCard(model)).join('');
html += '</div>';
}
container.innerHTML = html;
updateSelectedCount();
2026-01-29 17:34:59 +08:00
}
2026-01-20 21:46:18 +08:00
// 渲染单个模型卡片
function renderModelCard(model) {
const isSelected = selectedModels.has(model.id);
// 未合并的已训练模型不可选择
const isTrainedNotMerged = model.source === 'trained' && !model.merged;
const isDisabled = !isSelected && (selectedModels.size >= 2 || isTrainedNotMerged);
const typeText = {
'LLM': '大语言模型',
'CV': '计算机视觉',
'NLP': '自然语言处理',
'Embedding': '向量模型',
'Other': '其他'
}[model.type] || model.type || '其他';
// 处理字符串类型的ID
const modelId = typeof model.id === 'string' ? `'${model.id.replace(/'/g, "\\'")}'` : model.id;
// 未合并提示文字
const unmergedTip = isTrainedNotMerged ? '请先合并权重后再选择' : '';
return `
<div class="model-card border-b border-gray-100 last:border-b-0 p-4 cursor-pointer ${isSelected ? 'selected bg-blue-50/50' : ''} ${isDisabled ? 'disabled opacity-50' : ''} hover:bg-gray-50 transition-colors"
onclick="${isTrainedNotMerged ? `showMessage('提示', '该模型未合并权重,无法参与对比。请先在模型管理中合并权重。', 'warning')` : `toggleModel(${modelId})`}"
title="${unmergedTip}">
<div class="flex items-center justify-between">
<div class="flex items-center flex-1 min-w-0">
<div class="w-5 h-5 rounded-full border-2 ${isSelected ? 'border-primary bg-primary' : isTrainedNotMerged ? 'border-gray-300 bg-gray-200' : 'border-gray-300'} flex items-center justify-center mr-3 flex-shrink-0">
${isSelected ? '<i class="fa fa-check text-white text-xs"></i>' : ''}
</div>
<div class="min-w-0">
<div class="flex items-center mb-1">
<span class="font-medium text-gray-800 truncate">${model.name}</span>
<span class="ml-2 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded flex-shrink-0">${typeText}</span>
${isTrainedNotMerged ? '<span class="ml-2 px-2 py-0.5 bg-orange-100 text-orange-700 text-xs rounded flex-shrink-0"><i class="fa fa-lock mr-1"></i>未合并</span>' : ''}
${model.source === 'trained' && model.merged ? '<span class="ml-2 px-2 py-0.5 bg-green-100 text-green-700 text-xs rounded flex-shrink-0"><i class="fa fa-check-circle mr-1"></i>已合并</span>' : ''}
</div>
<p class="text-sm text-gray-500 truncate">${model.description || '暂无描述'}</p>
2026-01-29 17:34:59 +08:00
</div>
</div>
<div class="ml-4 text-xs text-gray-400 flex-shrink-0">
${model.source === 'trained' ? '<i class="fa fa-cog mr-1"></i>已训练' : (model.model_source === 'local' || model.model_source === undefined ? '<i class="fa fa-desktop mr-1"></i>本地' : '<i class="fa fa-cloud mr-1"></i>在线')}
</div>
2026-01-29 17:34:59 +08:00
</div>
</div>
`;
}
// 切换模型选择
function toggleModel(id) {
if (selectedModels.has(id)) {
// 移除模型时,释放其占用的端口
const config = selectedModels.get(id);
if (config && config.port) {
usedPorts.delete(parseInt(config.port));
}
selectedModels.delete(id);
} else if (selectedModels.size < 2) {
// 选择新模型时,分配默认端口 (7862, 7865, ...)
const defaultPorts = [7862, 7865, 7870, 7875, 7880, 7885, 7890, 7895];
let defaultPort = 7862;
for (const port of defaultPorts) {
if (!usedPorts.has(port)) {
defaultPort = port;
break;
}
}
usedPorts.add(defaultPort);
2026-01-20 22:24:11 +08:00
selectedModels.set(id, {
gpu_id: 0,
port: defaultPort
});
} else {
showMessage('提示', '最多只能选择2个模型进行对比', 'warning');
return;
}
renderModels();
renderSelectedModelsConfig();
2026-01-20 22:24:11 +08:00
}
// 渲染已选模型的配置区域
function renderSelectedModelsConfig() {
const configContainer = document.getElementById('configContainer');
const selectedConfigDiv = document.getElementById('selectedModelsConfig');
2026-01-29 17:34:59 +08:00
if (selectedModels.size === 0) {
selectedConfigDiv.style.display = 'none';
return;
}
2026-01-20 21:46:18 +08:00
selectedConfigDiv.style.display = 'block';
let html = '';
selectedModels.forEach((config, modelId) => {
const model = allModels.find(m => m.id === modelId);
if (!model) return;
// GPU选项 - 确保至少有默认选项
const gpuOptions = availableGPUs.length > 0
? availableGPUs.map(gpu =>
`<option value="${gpu.id}" ${config.gpu_id == gpu.id ? 'selected' : ''}>${gpu.name} (${gpu.memory})</option>`
).join('')
: '<option value="0">GPU 0 (不可用)</option>';
// 端口输入框
const portInputHtml = `
<input type="number" class="form-input"
value="${config.port}"
min="1024" max="65535"
onchange="updateModelConfig('${typeof modelId === 'string' ? modelId.replace(/'/g, "\\'") : modelId}', 'port', this.value)"
placeholder="请输入端口号">
<p class="text-xs text-gray-400 mt-1">建议范围: 7860-7999</p>
`;
const modelSourceLabel = model.source === 'trained' ? '已训练模型' : '本地模型';
html += `
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200" data-model-id="${modelId}">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center">
<span class="font-medium text-gray-800">${model.name}</span>
<span class="ml-2 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded">${modelSourceLabel}</span>
</div>
<button type="button" onclick="removeModel(${typeof modelId === 'string' ? `'${modelId.replace(/'/g, "\\'")}'` : modelId})"
class="text-red-500 hover:text-red-700 text-sm">
<i class="fa fa-trash-o mr-1"></i>移除
</button>
2026-01-29 17:34:59 +08:00
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="form-label">选择GPU <span class="text-red-500">*</span></label>
<select class="form-select" onchange="updateModelConfig('${typeof modelId === 'string' ? modelId.replace(/'/g, "\\'") : modelId}', 'gpu_id', this.value)">
${gpuOptions}
</select>
</div>
<div>
<label class="form-label">启动端口 <span class="text-red-500">*</span></label>
${portInputHtml}
2026-01-29 17:34:59 +08:00
</div>
</div>
</div>
`;
});
2026-01-29 17:34:59 +08:00
configContainer.innerHTML = html;
2026-01-20 21:46:18 +08:00
}
// 更新模型配置
window.updateModelConfig = function(modelId, key, value) {
const config = selectedModels.get(modelId);
if (config) {
// 如果是端口变更,需要更新已使用端口
if (key === 'port') {
usedPorts.delete(config.port);
usedPorts.add(parseInt(value));
}
config[key] = key === 'port' ? parseInt(value) : value;
selectedModels.set(modelId, config);
// 重新渲染配置区域以更新端口选项
renderSelectedModelsConfig();
}
};
2026-01-20 22:24:11 +08:00
// 移除模型
window.removeModel = function(modelId) {
const config = selectedModels.get(modelId);
if (config && config.port) {
usedPorts.delete(parseInt(config.port));
}
selectedModels.delete(modelId);
renderModels();
renderSelectedModelsConfig();
};
2026-01-20 21:46:18 +08:00
// 暴露到全局作用域供 onclick 调用
window.toggleModel = toggleModel;
window.goBack = goBack;
window.submitForm = submitForm;
// 筛选模型
function filterModels(keyword) {
const filtered = allModels.filter(model => {
const name = model.name?.toLowerCase() || '';
const desc = model.description?.toLowerCase() || '';
const type = model.type?.toLowerCase() || '';
const kw = keyword.toLowerCase();
return name.includes(kw) || desc.includes(kw) || type.includes(kw);
});
renderFilteredModels(filtered);
2026-01-20 22:24:11 +08:00
}
// 渲染筛选后的模型列表 - 使用分栏显示
function renderFilteredModels(models) {
const container = document.getElementById('modelList');
if (models.length === 0) {
container.innerHTML = `
<div class="p-8 text-center text-gray-400">
<i class="fa fa-search text-2xl mb-2"></i>
<p>未找到匹配的模型</p>
</div>
`;
return;
}
2026-01-29 17:34:59 +08:00
// 分离数据库模型和已训练模型
const dbModels = models.filter(m => m.source !== 'trained');
const trainedModels = models.filter(m => m.source === 'trained');
2026-01-29 17:34:59 +08:00
let html = '';
2026-01-29 17:34:59 +08:00
// 数据库模型区域
if (dbModels.length > 0) {
html += `
<div class="border-b border-gray-200">
<div class="px-4 py-2 bg-gray-50 text-xs font-medium text-gray-600 flex items-center">
<i class="fa fa-database mr-1"></i> 数据库模型 (${dbModels.length})
</div>
<div class="divide-y divide-gray-100">
`;
html += dbModels.map(model => renderModelCard(model)).join('');
html += '</div>';
}
2026-01-20 22:24:11 +08:00
// 已训练模型区域
if (trainedModels.length > 0) {
const mergedCount = trainedModels.filter(m => m.merged).length;
html += `
<div class="border-b border-gray-200">
<div class="px-4 py-2 bg-green-50 text-xs font-medium text-green-700 flex items-center justify-between">
<div class="flex items-center">
<i class="fa fa-check-circle mr-1"></i> 已训练模型 (${trainedModels.length})
</div>
<span class="text-orange-600">${mergedCount > 0 ? `${mergedCount}个已合并可选择` : '需合并后才可选择'}</span>
</div>
<div class="divide-y divide-gray-100">
`;
html += trainedModels.map(model => renderModelCard(model)).join('');
html += '</div>';
}
2026-01-20 21:46:18 +08:00
container.innerHTML = html;
2026-01-20 21:46:18 +08:00
}
// 更新已选数量
function updateSelectedCount() {
document.getElementById('selectedCount').textContent = selectedModels.size;
2026-01-20 21:46:18 +08:00
}
// 显示消息弹窗
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('modalConfirmBtn2');
modalTitle.textContent = title;
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>';
modalConfirmBtn.className = 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
} 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>';
modalConfirmBtn.className = 'px-6 py-2 bg-danger text-white rounded-lg hover:bg-danger/90 transition-colors';
} 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>';
modalConfirmBtn.className = 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
2026-01-20 21:46:18 +08:00
} 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>';
modalConfirmBtn.className = 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
2026-01-20 21:46:18 +08:00
}
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
2026-01-20 21:46:18 +08:00
modalConfirmBtn.onclick = () => {
closeModal();
if (onConfirm) onConfirm();
};
2026-01-20 21:46:18 +08:00
}
// 关闭消息弹窗
function closeModal() {
const modal = document.getElementById('customModal');
modal.classList.add('hidden');
document.body.style.overflow = '';
2026-01-20 21:46:18 +08:00
}
// 提交表单
async function submitForm() {
const form = document.getElementById('createForm');
const formData = new FormData(form);
const name = formData.get('name').trim();
const description = formData.get('description').trim();
2026-01-20 21:46:18 +08:00
if (!name) {
showMessage('提示', '请输入对比名称', 'warning');
return;
}
if (selectedModels.size < 2) {
showMessage('提示', '请选择2个模型进行对比', 'warning');
return;
}
// 构建模型配置数据
const modelsConfig = [];
selectedModels.forEach((config, modelId) => {
const model = allModels.find(m => m.id === modelId);
if (model) {
modelsConfig.push({
model_id: modelId,
model_name: model.name,
model_path: model.model_path || model.path || '',
gpu_id: parseInt(config.gpu_id) || 0,
port: parseInt(config.port) || 7862,
source: model.source || 'database'
});
}
2026-01-20 21:46:18 +08:00
});
const data = {
name: name,
description: description,
models: modelsConfig,
status: 'pending',
create_time: new Date().toLocaleString('zh-CN', { hour12: false }).replace(/\//g, '-')
};
console.log('[DEBUG] 提交数据:', JSON.stringify(data, null, 2));
try {
const response = await fetch(`${API_BASE}/model-compare`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
2026-01-20 21:46:18 +08:00
});
const result = await response.json();
if (result.code === 0) {
showMessage('成功', '对比任务创建成功!', 'success', () => {
window.location.href = 'main.html?page=model-compare';
});
2026-01-20 22:24:11 +08:00
} else {
showMessage('错误', result.message || '创建失败', 'error');
2026-01-20 22:24:11 +08:00
}
} catch (error) {
showMessage('错误', '创建失败: ' + error.message, 'error');
2026-01-20 21:46:18 +08:00
}
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 绑定侧边栏导航点击事件
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const page = this.dataset.page;
window.location.href = `main.html?page=${page}`;
});
2026-01-21 15:33:43 +08:00
});
// 绑定输入事件
const nameInput = document.querySelector('input[name="name"]');
const descInput = document.querySelector('textarea[name="description"]');
2026-01-20 21:46:18 +08:00
if (nameInput) {
nameInput.addEventListener('input', () => {
document.getElementById('nameCount').textContent = nameInput.value.length;
});
}
2026-01-20 21:46:18 +08:00
if (descInput) {
descInput.addEventListener('input', () => {
document.getElementById('descCount').textContent = descInput.value.length;
});
2026-01-20 23:08:02 +08:00
}
2026-01-20 21:46:18 +08:00
// 绑定模型搜索框事件
const modelSearchInput = document.getElementById('modelSearchInput');
if (modelSearchInput) {
modelSearchInput.addEventListener('input', (e) => {
const keyword = e.target.value.trim();
if (keyword) {
filterModels(keyword);
} else {
renderModels();
}
});
}
// 加载模型列表和GPU信息
loadModels();
// 设置侧边栏当前页高亮
const currentPage = 'model-compare';
document.querySelectorAll('.nav-link').forEach(link => {
if (link.dataset.page === currentPage) {
link.classList.add('sidebar-item-active');
link.classList.remove('hover:bg-[#001529]/20');
2026-01-20 22:24:11 +08:00
}
});
updateSidebarSlider();
2026-01-20 23:08:02 +08:00
});
// 更新侧边栏滑块位置
function updateSidebarSlider() {
const slider = document.getElementById('sidebar-slider');
if (!slider) return;
const activeLink = document.querySelector('.nav-link.bg-\\[\\#1890ff\\]\\/10');
if (activeLink) {
const wrapper = activeLink.closest('.nav-item-wrapper');
if (wrapper) {
slider.style.top = wrapper.offsetTop + 'px';
slider.style.height = wrapper.offsetHeight + 'px';
}
2026-01-21 15:33:43 +08:00
}
}
})();
2026-01-20 21:46:18 +08:00
</script>
</body>
</html>