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

660 lines
33 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="../lib/tailwindcss/tailwind.js"></script>
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<style>
/* 侧边栏滑块动画 */
.sidebar-slider {
position: absolute;
width: 4px;
height: 0;
background-color: #1890ff;
border-radius: 0 2px 2px 0;
transition: top 0.3s cubic-bezier(0.4, 0, 0.2, 1),
height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: none;
z-index: 10;
}
.sidebar-section-title {
padding: 0.5rem 1rem;
font-size: 0.75rem;
color: rgba(191, 203, 217, 0.7);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.nav-link:hover {
background-color: rgba(0, 21, 41, 0.2);
}
.nav-item-wrapper {
position: relative;
}
.nav-link {
position: relative;
z-index: 1;
}
.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;
}
.form-label {
display: block;
font-size: 0.875rem;
font-weight: 500;
color: #374151;
margin-bottom: 0.25rem;
}
.form-select {
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;
appearance: none;
background-color: white;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
}
.form-select:focus {
border-color: #1890ff;
outline: none;
}
.card-radio {
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
padding: 1rem;
cursor: pointer;
transition: all 0.2s;
}
.card-radio.active {
border-color: #1890ff;
background-color: rgba(24, 144, 255, 0.05);
}
.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">
<!-- 侧边导航 -->
<aside class="w-64 text-[#bfcbd9] flex-shrink-0 hidden md:block flex flex-col h-full" style="background-color: #001529;">
<!-- 平台LOGO区域 -->
<div class="pt-5 pb-3 border-b border-[#001529]/30 flex items-center justify-center pl-2">
<img src="../assets/logo/logo.png" alt="Logo" class="w-8 h-8 object-contain mr-2">
<span class="text-white font-medium text-base">远光软件微调平台</span>
</div>
<!-- 导航主区域 -->
<nav class="flex-1 overflow-y-auto py-2 relative">
<!-- 滑块指示器 -->
<div class="sidebar-slider" id="sidebar-slider"></div>
<!-- 第一分区:模型服务 -->
<div class="sidebar-section-title">模型服务</div>
<div class="nav-item-wrapper">
<a href="main.html" data-page="fine-tune" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-cogs w-5 text-center"></i>
<span class="ml-2">模型调优</span>
</a>
</div>
<div class="nav-item-wrapper">
<a href="main.html?page=my-models" data-page="my-models" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-database w-5 text-center"></i>
<span class="ml-2">我的模型</span>
</a>
</div>
<div class="nav-item-wrapper">
<a href="main.html?page=model-eval" data-page="model-eval" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-line-chart w-5 text-center"></i>
<span class="ml-2">模型评测</span>
</a>
</div>
<div class="nav-item-wrapper">
<a href="main.html?page=model-compare" data-page="model-compare" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-server w-5 text-center"></i>
<span class="ml-2">模型对比</span>
</a>
</div>
<!-- 第二分区:资源管理 -->
<div class="sidebar-section-title mt-6">资源管理</div>
<div class="nav-item-wrapper">
<a href="main.html?page=model-manage" data-page="model-manage" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-cube w-5 text-center"></i>
<span class="ml-2">模型管理</span>
</a>
</div>
<div class="nav-item-wrapper">
<a href="main.html?page=dataset-manage" data-page="dataset-manage" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-file-text w-5 text-center"></i>
<span class="ml-2">数据集管理</span>
</a>
</div>
<div class="nav-item-wrapper">
<a href="main.html?page=data-generate" data-page="data-generate" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-database w-5 text-center"></i>
<span class="ml-2">其他工具</span>
</a>
</div>
<!-- 第三分区:系统设置 -->
<div class="sidebar-section-title mt-6">系统设置</div>
<div class="nav-item-wrapper">
<a href="main.html?page=config" data-page="config" class="nav-link flex items-center px-4 py-2.5 hover:bg-[#001529]/20 transition-colors">
<i class="fa fa-bar-chart w-5 text-center"></i>
<span class="ml-2">平台性能</span>
</a>
</div>
</nav>
<!-- 底部信息区域 -->
<div class="p-4 border-t border-[#001529]/30 text-xs mt-auto">
<div class="mb-2 text-[#bfcbd9]/80">默认业务空间</div>
<div class="flex items-center justify-between">
<span class="text-[#bfcbd9]">版本 v1.0.0</span>
<i class="fa fa-question-circle-o text-[#bfcbd9]/70"></i>
</div>
</div>
</aside>
<!-- 主内容区 -->
<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="main.html?page=model-eval" 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">
<!-- 页面标题 -->
<div class="bg-white rounded-lg shadow-sm p-4 border-b border-gray-100 mb-4">
<div class="flex items-center text-sm">
<span class="text-primary cursor-pointer hover:underline" onclick="window.location.href='main.html?page=model-eval'">模型评测</span>
<span class="mx-2 text-gray-300">/</span>
<span class="text-gray-800 font-medium">新建评测</span>
</div>
</div>
<!-- 表单内容 -->
<div class="bg-white rounded-lg shadow-sm">
<div class="p-6">
<form id="evalForm">
<!-- 任务名称 -->
<div class="mb-6">
<label class="form-label">
<span class="text-red-500 mr-1">*</span>任务名称
</label>
<div class="flex items-center">
<input type="text" name="name" id="taskName" class="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-primary focus:outline-none" placeholder="请输入任务名称" maxlength="50">
<span class="text-xs text-gray-400 ml-2"><span id="nameCount">0</span> / 50</span>
</div>
</div>
<!-- 评测方式 -->
<div class="mb-6">
<label class="form-label">
<span class="text-red-500 mr-1">*</span>评测方式
</label>
<div class="grid grid-cols-2 gap-4">
<label class="border-2 border-primary bg-blue-50 rounded-lg p-4 cursor-pointer" id="customEvalLabel">
<input type="radio" name="eval_type" value="custom" checked class="text-primary focus:ring-primary sr-only" onchange="toggleEvalType('custom')">
<div class="font-medium text-gray-800">自定义评测</div>
<p class="text-xs text-gray-500 mt-1">支持基于AI评测、自动化指标计算、人工评测方式<br>自定义评测维度,可自由组合上述各类维度进行评测。</p>
</label>
<label class="border border-gray-200 rounded-lg p-4 cursor-pointer hover:border-primary/50" id="baselineEvalLabel">
<input type="radio" name="eval_type" value="baseline" class="text-primary focus:ring-primary sr-only" onchange="toggleEvalType('baseline')">
<div class="font-medium text-gray-800">基线评测</div>
<p class="text-xs text-gray-500 mt-1">通过预置基线评测集,评测模型基本能力,自动生成评测报告</p>
</label>
</div>
</div>
<!-- 评测对象 -->
<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="max-w-md">
<!-- 评测模型 -->
<div class="mb-4">
<label class="block text-sm text-gray-600 mb-1">
<span class="text-red-500 mr-1">*</span>评测模型
</label>
<select name="model_id" id="modelSelect" class="form-select flex-1 max-w-md">
<option value="">请选择</option>
</select>
</div>
<!-- 数据来源 -->
<div class="mb-4">
<label class="block text-sm text-gray-600 mb-2">
<span class="text-red-500 mr-1">*</span>数据来源
</label>
<div class="flex items-center space-x-6">
<label class="flex items-center cursor-pointer">
<input type="radio" name="data_source" value="dataset" checked class="mr-2" onchange="toggleDataSource('dataset')">
<span class="text-sm text-gray-700">评测数据集</span>
</label>
<label class="flex items-center cursor-pointer">
<input type="radio" name="data_source" value="inference" class="mr-2" onchange="toggleDataSource('inference')">
<span class="text-sm text-gray-700">推理结果集</span>
</label>
</div>
</div>
<!-- 推理结果集上传 -->
<div id="inferenceUpload" class="hidden border-2 border-dashed border-gray-200 rounded-lg p-8 text-center hover:border-primary/50 transition-colors cursor-pointer">
<i class="fa fa-cloud-upload text-gray-400 text-xl mb-2"></i>
<p class="text-gray-700 text-sm">点击或拖拽上传推理结果集</p>
<p class="text-xs text-gray-400 mt-1">支持 .xls .xlsx 格式不超过2MB</p>
<a href="#" class="text-primary text-xs mt-2 inline-block hover:underline">下载模板</a>
</div>
</div>
</div>
<!-- 评测数据集选择 -->
<div id="datasetSelect" 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">
<select name="dataset_id" id="testDatasetSelect" 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="loadTestDatasets()">
<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'">
+ 新增数据集
</button>
</div>
</div>
<!-- 评测规则 -->
<div class="mb-6" id="evalRulesSection">
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">评测规则</h3>
<div class="flex items-center">
<select name="eval_dimension" id="dimensionSelect" 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="loadDimensions()">
<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='model-dimension-create.html'">
+ 创建评测维度
</button>
</div>
</div>
<!-- Leaderboard -->
<div class="mb-6">
<h3 class="text-sm font-semibold text-gray-700 mb-4 pb-2 border-b border-gray-100">Leaderboard</h3>
<div class="flex items-center">
<label class="text-gray-700 mr-4">是否参与排行</label>
<div class="relative inline-block w-10 h-5">
<input type="checkbox" name="leaderboard" class="opacity-0 w-0 h-0" onchange="toggleLeaderboard(this)">
<div class="bg-gray-200 rounded-full absolute inset-0 peer-focus:ring-2 peer-focus:ring-primary/30"></div>
<div class="bg-white w-4 h-4 rounded-full absolute left-0.5 top-0.5 transition-transform" id="leaderboardToggle"></div>
</div>
</div>
</div>
<!-- 底部操作 -->
<div class="flex justify-between items-center 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">
开始评测
</button>
<a href="main.html?page=model-eval" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300">
取消
</a>
</div>
<div class="text-gray-400 text-xs">
费用 以实际发生为准 <a href="#" class="text-primary hover:underline">计算详情</a>
</div>
</div>
</form>
</div>
</div>
</main>
</div>
<script>
// API 基础地址
const getApiBase = () => {
const protocol = window.location.protocol;
const hostname = window.location.hostname;
return `${protocol}//${hostname}:8080/api`;
};
const API_BASE = getApiBase();
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 任务名称字数统计
const nameInput = document.getElementById('taskName');
nameInput.addEventListener('input', () => {
document.getElementById('nameCount').textContent = nameInput.value.length;
});
// 初始化评测方式样式
updateEvalTypeStyle('custom');
// 初始化数据来源显示
toggleDataSource('dataset');
// 加载模型列表
loadModels();
// 加载评测数据集
loadTestDatasets();
// 加载评测维度
loadDimensions();
// 设置侧边栏当前页高亮
const currentPage = 'model-eval';
document.querySelectorAll('.nav-link').forEach(link => {
if (link.dataset.page === currentPage) {
link.classList.add('bg-[#1890ff]/10', 'text-[#1890ff]');
link.classList.remove('hover:bg-[#001529]/20', 'transition-colors');
}
});
// 更新滑块位置
updateSidebarSlider();
// 绑定导航点击事件
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', function(e) {
if (!this.href.includes('model-eval-create')) {
e.preventDefault();
window.location.href = this.href;
}
});
});
});
// 更新侧边栏滑块位置
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';
}
}
}
// 切换评测方式
function toggleEvalType(type) {
updateEvalTypeStyle(type);
const rulesSection = document.getElementById('evalRulesSection');
if (type === 'baseline') {
rulesSection.classList.add('hidden');
} else {
rulesSection.classList.remove('hidden');
}
}
function updateEvalTypeStyle(type) {
const customLabel = document.getElementById('customEvalLabel');
const baselineLabel = document.getElementById('baselineEvalLabel');
if (type === 'custom') {
customLabel.classList.add('border-primary', 'bg-blue-50');
customLabel.classList.remove('border-gray-200');
baselineLabel.classList.remove('border-primary', 'bg-blue-50');
baselineLabel.classList.add('border-gray-200');
} else {
baselineLabel.classList.add('border-primary', 'bg-blue-50');
baselineLabel.classList.remove('border-gray-200');
customLabel.classList.remove('border-primary', 'bg-blue-50');
customLabel.classList.add('border-gray-200');
}
}
// 切换数据来源
function toggleDataSource(source) {
const inferenceUpload = document.getElementById('inferenceUpload');
const datasetSelect = document.getElementById('datasetSelect');
if (source === 'inference') {
inferenceUpload.classList.remove('hidden');
datasetSelect.classList.add('hidden');
} else {
inferenceUpload.classList.add('hidden');
datasetSelect.classList.remove('hidden');
}
}
// 切换Leaderboard开关样式
function toggleLeaderboard(checkbox) {
const toggle = document.getElementById('leaderboardToggle');
if (checkbox.checked) {
toggle.style.transform = 'translateX(20px)';
toggle.previousElementSibling.classList.remove('bg-gray-200');
toggle.previousElementSibling.classList.add('bg-primary');
} else {
toggle.style.transform = 'translateX(0)';
toggle.previousElementSibling.classList.remove('bg-primary');
toggle.previousElementSibling.classList.add('bg-gray-200');
}
}
// 加载模型列表
async function loadModels() {
try {
const response = await fetch(`${API_BASE}/model-manage`);
const result = await response.json();
if (result.code === 0) {
const select = document.getElementById('modelSelect');
select.innerHTML = '<option value="">请选择</option>' +
result.data.map(m => `<option value="${m.id}">${m.name}</option>`).join('');
}
} catch (e) {
console.error('加载模型失败:', e);
}
}
// 加载评测数据集type="eval"
async function loadTestDatasets() {
try {
const response = await fetch(`${API_BASE}/dataset-manage`);
const result = await response.json();
if (result.code === 0) {
// 筛选类型为 eval 的数据集
const evalDatasets = (result.data || []).filter(d => d.type === 'eval');
const select = document.getElementById('testDatasetSelect');
select.innerHTML = '<option value="">请选择评测数据集</option>' +
evalDatasets.map(d => `<option value="${d.id}">${d.name}</option>`).join('');
}
} catch (e) {
console.error('加载评测数据集失败:', e);
}
}
// 加载评测维度列表
async function loadDimensions() {
try {
const response = await fetch(`${API_BASE}/dimension`);
const result = await response.json();
if (result.code === 0 && result.data) {
const select = document.getElementById('dimensionSelect');
select.innerHTML = '<option value="">请选择评测维度</option>' +
result.data.map(d => `<option value="${d.id}">${d.name} (${getDimensionTypeName(d.type)})</option>`).join('');
}
} catch (e) {
console.error('加载评测维度失败:', e);
}
}
// 获取评测维度类型名称
function getDimensionTypeName(type) {
const typeMap = {
'classification': '分类',
'metric': '指标',
'text_similarity': '文本相似度'
};
return typeMap[type] || type || '未知';
}
// 提交表单
async function submitForm() {
const form = document.getElementById('evalForm');
const formData = new FormData(form);
const data = {
name: formData.get('name'),
eval_type: formData.get('eval_type'),
model_id: formData.get('model_id'),
dataset_id: formData.get('dataset_id'),
eval_dimension: formData.get('eval_dimension'),
data_source: formData.get('data_source'),
leaderboard: formData.get('leader') === 'on'
};
if (!data.name) {
showMessage('提示', '请输入任务名称', 'warning');
return;
}
if (!data.model_id) {
showMessage('提示', '请选择评测模型', 'warning');
return;
}
if (!data.dataset_id) {
showMessage('提示', '请选择评测数据集', 'warning');
return;
}
if (!data.eval_dimension) {
showMessage('提示', '请选择评测维度', 'warning');
return;
}
try {
const response = await fetch(`${API_BASE}/model-eval`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.code === 0) {
showMessage('成功', '创建成功!', 'success', () => {
window.location.href = 'main.html?page=model-eval';
});
} 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-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-base 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 flex-col space-y-2 mx-4">
<button id="modalConfirmBtn" class="px-4 py-2 w-full text-white rounded transition-colors text-sm">确定</button>
<button id="modalCancelBtn" class="px-4 py-2 w-full border border-gray-300 text-gray-700 rounded hover:bg-gray-50 transition-colors text-sm">取消</button>
</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>
// 显示消息弹窗
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';
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>';
}
modalMessage.innerHTML = message;
modalBtnGroup.classList.add('hidden');
modalSingleBtnGroup.classList.remove('hidden');
const confirmBtn = modalConfirmBtn2;
confirmBtn.className = 'px-6 py-2 w-full text-white rounded transition-colors text-sm max-w-[160px]';
if (type === 'success') {
confirmBtn.classList.add('bg-primary');
} else if (type === 'error') {
confirmBtn.classList.add('bg-red-500');
} else if (type === 'warning') {
confirmBtn.classList.add('bg-yellow-500');
} else {
confirmBtn.classList.add('bg-primary');
}
confirmBtn.onclick = () => {
closeModal();
if (onConfirm) onConfirm();
};
modal.classList.remove('hidden');
}
function closeModal() {
const modal = document.getElementById('customModal');
modal.classList.add('hidden');
}
</script>
</body>
</html>