Files
YG_FT_Platform/web/pages/model-manage.html

736 lines
34 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>
<script>
// 禁用 Tailwind 开发模式警告
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>
<link href="../lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<style>
.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);
}
.sidebar-item-active {
background-color: rgba(24, 144, 255, 0.1);
color: #1890ff;
border-left: 4px solid #1890ff;
}
.bg-primary { background-color: #1890ff; }
.text-primary { color: #1890ff; }
.border-primary { border-color: #1890ff; }
:root { --primary: #1890ff; }
.form-input {
display: block;
width: 100%;
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
line-height: 1.25rem;
color: #1f2937;
background-color: #fff;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
transition: border-color 0.15s ease-in-out;
}
.form-input:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.1);
}
.form-label {
display: block;
font-size: 0.875rem;
font-weight: 500;
color: #374151;
margin-bottom: 0.25rem;
}
.tab-btn {
padding: 8px 20px !important;
font-size: 14px !important;
font-weight: 500 !important;
border-radius: 6px !important;
border: 1px solid transparent !important;
cursor: pointer !important;
background: transparent !important;
color: #4b5563 !important;
transition: all 0.2s !important;
min-width: 100px;
}
.tab-btn:hover {
background: white !important;
border-color: #d1d5db !important;
}
.tab-btn.tab-active {
background: white !important;
color: #1890ff !important;
border-color: #1890ff !important;
box-shadow: 0 1px 3px rgba(24,144,255,0.2) !important;
}
</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-section-title">模型服务</div>
<div class="nav-item-wrapper">
<a href="#" 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="#" 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="#" 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="#" data-page="model-manage" class="nav-link sidebar-item-active 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="#" 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="#" 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="#" 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>
<div class="nav-item-wrapper">
<a href="#" data-page="logs" 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>
</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-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">
<!-- 系统性能监控 -->
<a href="?page=config" class="flex items-center space-x-4 text-xs text-gray-500 hover:text-primary transition-colors">
<div class="flex items-center" title="CPU使用率">
<i class="fa fa-microchip mr-1 text-blue-500"></i>
<span id="cpuUsage">--</span>%
</div>
<div class="flex items-center" title="内存使用率">
<i class="fa fa-database mr-1 text-green-500"></i>
<span id="memUsage">--</span>%
</div>
<div class="flex items-center" title="磁盘使用率">
<i class="fa fa-hdd-o mr-1 text-orange-500"></i>
<span id="diskUsage">--</span>%
</div>
</a>
<div class="h-6 w-px bg-gray-200"></div>
<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 pt-2 hidden group-hover:block z-50">
<div class="bg-white rounded shadow-lg py-1 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>
</div>
</header>
<!-- 页面内容 -->
<main class="flex-1 overflow-y-auto">
<!-- Tab 导航 -->
<div style="background: #fff; border-bottom: 1px solid #e5e7eb; padding: 16px;">
<h2 style="font-size: 18px; font-weight: 600; color: #1f2937; margin-bottom: 12px;">模型管理</h2>
<div style="display: flex; gap: 8px; background: #f3f4f6; padding: 6px; border-radius: 8px;">
<button onclick="switchTab('all')" id="tab-all" class="tab-btn tab-active" style="display: inline-flex; align-items: center; justify-content: center;">
全部模型
</button>
<button onclick="switchTab('training')" id="tab-training" class="tab-btn" style="display: inline-flex; align-items: center; justify-content: center;">
训练基座
</button>
<button onclick="switchTab('inference')" id="tab-inference" class="tab-btn" style="display: inline-flex; align-items: center; justify-content: center;">
推理对比
</button>
<button onclick="switchTab('evaluation')" id="tab-evaluation" class="tab-btn" style="display: inline-flex; align-items: center; justify-content: center;">
评测模型
</button>
<button onclick="switchTab('trained')" id="tab-trained" class="tab-btn" style="display: inline-flex; align-items: center; justify-content: center;">
已训练模型
</button>
</div>
</div>
<!-- 表格容器 -->
<div style="padding: 16px;">
<!-- 工具栏 -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<div style="display: flex; align-items: center; gap: 16px;">
<input type="text" id="searchInput" placeholder="搜索模型名称..." style="width: 256px; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px;" oninput="filterModels()">
</div>
<button onclick="window.location.href='model-manage-create.html'" style="padding: 8px 16px; background: #1890ff; color: white; border: none; border-radius: 6px; cursor: pointer; display: flex; align-items: center; font-size: 14px;">
<span style="margin-right: 8px;">+</span>添加模型
</button>
</div>
<!-- 模型表格 -->
<div id="modelsTableContainer" class="bg-white rounded-lg shadow-sm">
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">模型名称</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">模型类型</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">用途</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">模型来源</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">描述</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">模型路径</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">创建时间</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
</tr>
</thead>
<tbody id="modelsBody" class="bg-white divide-y divide-gray-200">
<!-- 动态加载 -->
</tbody>
</table>
</div>
<!-- 空状态 -->
<div id="emptyState" class="hidden px-6 py-12 text-center">
<i class="fa fa-inbox text-4xl text-gray-300 mb-3"></i>
<p class="text-gray-500">暂无模型数据</p>
</div>
</div>
<!-- 已训练模型表格 -->
<div id="trainedModelsContainer" class="hidden bg-white rounded-lg shadow-sm">
<div class="p-4 border-b border-gray-200">
<p class="text-sm text-gray-500">已训练模型存储在 /app/base/saves 目录下</p>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">基座模型</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">训练方法</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">模型路径</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
</tr>
</thead>
<tbody id="trainedModelsBody" class="bg-white divide-y divide-gray-200">
<!-- 动态加载 -->
</tbody>
</table>
</div>
<!-- 空状态 -->
<div id="trainedEmptyState" class="hidden px-6 py-12 text-center">
<i class="fa fa-inbox text-4xl text-gray-300 mb-3"></i>
<p class="text-gray-500">暂无已训练模型</p>
</div>
</div>
</main>
<script>
console.log('[DEBUG] model-manage.html 脚本开始加载');
// 使用 IIFE 避免全局变量污染
(function() {
console.log('[DEBUG] model-manage.html IIFE 开始执行');
// 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();
let allModels = [];
let trainedModels = [];
let currentTab = 'all';
// Tab 切换
function switchTab(tab) {
currentTab = tab;
// 更新按钮样式
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('tab-active');
});
const activeTab = document.getElementById(`tab-${tab}`);
activeTab.classList.add('tab-active');
// 显示/隐藏搜索框和添加按钮
const toolbar = document.querySelector('div[style*="justify-content: space-between"]');
if (toolbar) {
toolbar.style.display = tab === 'trained' ? 'none' : 'flex';
}
// 显示/隐藏表格容器
const modelsTable = document.getElementById('modelsTableContainer');
const trainedModelsContainer = document.getElementById('trainedModelsContainer');
if (tab === 'trained') {
modelsTable.classList.add('hidden');
trainedModelsContainer.classList.remove('hidden');
loadTrainedModels();
} else {
modelsTable.classList.remove('hidden');
trainedModelsContainer.classList.add('hidden');
renderModels();
}
}
// 加载模型数据
async function loadModels() {
try {
// 并行加载数据库模型和已训练模型
const [dbResponse, trainedResponse] = await Promise.all([
fetch(`${API_BASE}/model-manage`),
fetch(`${API_BASE}/model-manage/trained-models`)
]);
const dbResult = await dbResponse.json();
const trainedResult = await trainedResponse.json();
console.log('[DEBUG] 数据库模型数量:', dbResult.data?.length || 0);
console.log('[DEBUG] 已训练模型API响应:', trainedResult);
// 数据库模型
let dbModels = [];
if (dbResult.code === 0) {
dbModels = dbResult.data || [];
}
// 已训练模型 - 转换为统一格式
trainedModels = [];
if (trainedResult.code === 0) {
const trainedData = trainedResult.data?.models || [];
console.log('[DEBUG] 已训练模型数据:', trainedData);
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',
purpose: 'inference',
model_source: 'local',
path: method.path,
description: `基于 ${model.name}${getMethodDisplayName(method.name)}训练模型`,
create_time: new Date().toISOString(),
isTrained: true,
baseModel: model.name,
trainMethod: method.name
});
});
} else {
// 没有训练方法的也添加为模型
trainedModels.push({
id: `trained_${model.name}`.replace(/[^a-zA-Z0-9]/g, '_'),
name: model.name,
type: 'LLM',
purpose: 'inference',
model_source: 'local',
path: model.path,
description: '已训练模型',
create_time: new Date().toISOString(),
isTrained: true,
baseModel: model.name,
trainMethod: ''
});
}
});
}
// 合并所有模型
allModels = [...dbModels, ...trainedModels];
console.log('[DEBUG] 数据库模型:', dbModels.length);
console.log('[DEBUG] 已训练模型:', trainedModels.length);
console.log('[DEBUG] 合并后的模型总数:', allModels.length);
renderModels();
} catch (error) {
console.error('加载模型失败:', error);
}
}
// 获取训练方法显示名称
function getMethodDisplayName(method) {
const methodMap = {
'lora': 'LoRA',
'qlora': 'QLoRA',
'full': '全量微调',
'prefix': 'Prefix Tuning',
'adapter': 'Adapter',
'lora_plus': 'LoRA+',
'peft': 'PEFT',
'adalora': 'AdaLoRA',
'longlora': 'LongLoRA'
};
return methodMap[method] || method;
}
// 加载已训练模型数据仅用于Trained Tab
async function loadTrainedModels() {
try {
const response = await fetch(`${API_BASE}/model-manage/trained-models`);
const result = await response.json();
console.log('[DEBUG] 已训练模型:', result);
if (result.code === 0) {
trainedModels = result.data?.models || [];
renderTrainedModels();
}
} catch (error) {
console.error('加载已训练模型失败:', error);
}
}
// 筛选模型
function filterModels() {
renderModels();
}
// 渲染模型列表
function renderModels() {
const searchInput = document.getElementById('searchInput');
const searchValue = searchInput ? searchInput.value.toLowerCase() : '';
let filteredModels = allModels;
// 按 Tab 筛选
if (currentTab !== 'all') {
filteredModels = filteredModels.filter(m => m.purpose === currentTab);
}
// 按搜索关键词筛选
if (searchValue) {
filteredModels = filteredModels.filter(m =>
m.name?.toLowerCase().includes(searchValue) ||
m.description?.toLowerCase().includes(searchValue)
);
}
const tbody = document.getElementById('modelsBody');
const emptyState = document.getElementById('emptyState');
if (!tbody) return;
if (filteredModels.length === 0) {
tbody.innerHTML = '';
if (emptyState) emptyState.classList.remove('hidden');
return;
}
if (emptyState) emptyState.classList.add('hidden');
tbody.innerHTML = filteredModels.map(item => {
// 模型类型
const typeMap = {
'LLM': '大语言模型',
'CV': '计算机视觉',
'NLP': '自然语言处理',
'Embedding': '向量模型',
'Other': '其他'
};
const typeDisplay = typeMap[item.type] || item.type || '-';
// 用途
const purposeMap = {
'training': { text: '训练', class: 'bg-blue-100 text-blue-700' },
'inference': { text: '推理', class: 'bg-green-100 text-green-700' },
'evaluation': { text: '评测', class: 'bg-purple-100 text-purple-700' }
};
const purposeDisplay = purposeMap[item.purpose] || purposeMap['inference'];
// 模型来源
const sourceMap = {
'local': '本地模型',
'api': '在线模型',
'online': '在线模型'
};
const sourceDisplay = sourceMap[item.model_source] || item.model_source || '-';
// 判断是否是已训练模型
const isTrained = item.isTrained === true;
const trainedBadge = isTrained ? '<span class="ml-2 px-2 py-0.5 text-xs font-medium rounded bg-green-100 text-green-700">已训练</span>' : '';
// 操作按钮
let actionButtons = '';
if (isTrained) {
// 已训练模型:显示查看和复制路径按钮
actionButtons = `
<button onclick="viewTrainedModel('${(item.path || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'")}')" class="text-primary hover:text-primary/80 mr-3">
<i class="fa fa-folder-open"></i> 查看
</button>
<button onclick="copyModelPath('${(item.path || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'")}')" class="text-gray-500 hover:text-gray-700">
<i class="fa fa-copy"></i> 复制路径
</button>
`;
} else {
// 普通模型:编辑和删除
actionButtons = `
<button onclick="editModel(${item.id})" class="text-primary hover:text-primary/80 mr-3">
<i class="fa fa-edit"></i> 编辑
</button>
<button onclick="deleteModel(${item.id})" class="text-red-500 hover:text-red-600">
<i class="fa fa-trash"></i> 删除
</button>
`;
}
return `
<tr class="hover:bg-gray-50 ${isTrained ? 'bg-green-50/30' : ''}">
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">${item.name || '-'}${trainedBadge}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-medium rounded bg-blue-100 text-blue-700">${typeDisplay}</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-medium rounded ${purposeDisplay.class}">${purposeDisplay.text}</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-medium rounded bg-gray-100 text-gray-700">${sourceDisplay}</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-500 max-w-xs truncate" title="${item.description || ''}">${item.description || '-'}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-500 max-w-xs truncate ${isTrained ? 'font-mono text-green-600' : ''}" title="${item.path || ''}">${item.path ? (isTrained ? item.path.split('/').pop() : item.path) : '-'}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
${item.create_time ? new Date(item.create_time).toLocaleString('zh-CN') : '-'}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
${actionButtons}
</td>
</tr>
`;
}).join('');
}
// 渲染已训练模型列表
function renderTrainedModels() {
const tbody = document.getElementById('trainedModelsBody');
const emptyState = document.getElementById('trainedEmptyState');
// 收集所有训练方法
let allTrainMethods = [];
trainedModels.forEach(model => {
if (model.train_methods && model.train_methods.length > 0) {
model.train_methods.forEach(method => {
allTrainMethods.push({
baseModel: model.name,
trainMethod: method.name,
path: method.path
});
});
}
});
if (allTrainMethods.length === 0) {
tbody.innerHTML = '';
emptyState.classList.remove('hidden');
return;
}
emptyState.classList.add('hidden');
tbody.innerHTML = allTrainMethods.map(item => {
// 训练方法显示
const methodMap = {
'lora': 'LoRA',
'qlora': 'QLoRA',
'full': '全量微调',
'prefix': 'Prefix Tuning',
'adapter': 'Adapter'
};
const methodDisplay = methodMap[item.trainMethod] || item.trainMethod;
return `
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">${item.baseModel}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-medium rounded bg-green-100 text-green-700">${methodDisplay}</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-500 max-w-xs truncate" title="${item.path}">${item.path}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<button onclick="viewTrainedModel('${item.path.replace(/\\/g, '\\\\')}')" class="text-primary hover:text-primary/80 mr-3">
<i class="fa fa-folder-open"></i> 查看
</button>
</td>
</tr>
`;
}).join('');
}
// 查看已训练模型
function viewTrainedModel(path) {
// 显示模型路径详情
const content = `
<div class="text-left">
<p class="mb-3 text-gray-600">模型路径:</p>
<div class="bg-gray-100 p-3 rounded text-sm font-mono break-all mb-3">${path}</div>
<p class="text-sm text-gray-500">您可以复制此路径用于推理或评测。</p>
</div>
`;
// 使用简单的提示框
showModal('模型详情', content);
}
// 复制模型路径
function copyModelPath(path) {
navigator.clipboard.writeText(path).then(() => {
showToast('模型路径已复制到剪贴板');
}).catch(() => {
// 降级方案
const textarea = document.createElement('textarea');
textarea.value = path;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
showToast('模型路径已复制到剪贴板');
});
}
// 显示弹窗
function showModal(title, content) {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
modal.innerHTML = `
<div class="bg-white rounded-lg p-6 max-w-lg w-full mx-4">
<h3 class="text-lg font-medium mb-4">${title}</h3>
<div class="mb-4">${content}</div>
<div class="flex justify-end">
<button onclick="this.closest('.fixed').remove()" class="px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300">关闭</button>
</div>
</div>
`;
document.body.appendChild(modal);
}
// 显示提示
function showToast(message) {
const toast = document.createElement('div');
toast.className = 'fixed bottom-4 right-4 bg-gray-800 text-white px-4 py-2 rounded shadow-lg z-50';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 2000);
}
// 编辑模型
function editModel(id) {
window.location.href = `model-manage-create.html?id=${id}`;
}
// 删除模型
async function deleteModel(id) {
if (!confirm('确定要删除此模型吗?')) return;
try {
const response = await fetch(`${API_BASE}/model-manage/${id}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.code === 0) {
loadModels();
} else {
alert('删除失败: ' + result.message);
}
} catch (error) {
console.error('删除模型失败:', error);
alert('删除失败');
}
}
// 页面加载时初始化
try {
loadModels();
} catch (e) {
console.error('初始化失败:', e);
}
})();
</script>
</body>
</html>