Files
YG_FT_Platform/web/pages/custom-tool-create.html
2026-01-26 14:38:43 +08:00

525 lines
24 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-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);
}
.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;
}
.icon-option {
width: 2.5rem;
height: 2.5rem;
border-radius: 0.5rem;
border: 1px solid #e5e7eb;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.icon-option:hover {
border-color: #1890ff;
background-color: rgba(24, 144, 255, 0.05);
}
.icon-option.selected {
border-color: #1890ff;
background-color: rgba(24, 144, 255, 0.1);
}
.bg-primary { background-color: #1890ff; }
.text-primary { color: #1890ff; }
.border-primary { border-color: #1890ff; }
:root { --primary: #1890ff; --danger: #f5222d; --success: #52c41a; }
/* 侧边栏滑块动画 */
.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;
}
.nav-item-wrapper {
position: relative;
}
.nav-link {
position: relative;
z-index: 1;
}
</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>
<div class="nav-item-wrapper">
<a href="main.html?page=logs" 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-4">
<a href="main.html?page=data-generate" 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=data-generate'">其他工具</span>
<span class="mx-2 text-gray-300">/</span>
<span class="text-gray-800 font-medium" id="pageTitle">添加自定义工具</span>
</div>
</div>
<!-- 表单内容 -->
<div class="bg-white rounded-lg shadow-sm">
<div class="p-6 max-w-2xl">
<form id="toolForm">
<!-- 工具名称 -->
<div class="mb-6">
<label class="form-label">
<span class="text-red-500 mr-1">*</span>工具名称
</label>
<input type="text" name="name" class="form-input" placeholder="请输入工具名称" maxlength="30">
<p class="text-xs text-gray-400 mt-1">支持中文、英文、数字最多30个字符</p>
</div>
<!-- 工具描述 -->
<div class="mb-6">
<label class="form-label">工具描述</label>
<textarea name="description" rows="3" class="form-input resize-none" placeholder="请输入工具描述" maxlength="100"></textarea>
<p class="text-xs text-gray-400 mt-1"><span id="descCount">0</span> / 100</p>
</div>
<!-- 跳转地址 -->
<div class="mb-6">
<label class="form-label">
<span class="text-red-500 mr-1">*</span>跳转地址
</label>
<input type="text" name="url" class="form-input" placeholder="如custom-tool.html 或 https://example.com">
<p class="text-xs text-gray-400 mt-1">输入工具页面的相对路径或完整URL</p>
</div>
<!-- 图标选择 -->
<div class="mb-6">
<label class="form-label">选择图标</label>
<div class="grid grid-cols-8 gap-2" id="iconGrid"></div>
<input type="hidden" id="selectedIcon" value="fa-cog" name="icon">
</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">
<i class="fa fa-check mr-2"></i>保存
</button>
<a href="main.html?page=data-generate" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300 transition-colors">
<i class="fa fa-times mr-2"></i>取消
</a>
</div>
</div>
</form>
</div>
</div>
</main>
</div>
<script>
// API 基础地址 - 使用 config.yaml 中的 app.port (7861)
const getApiBase = () => {
const protocol = window.location.protocol;
const hostname = window.location.hostname;
return `${protocol}//${hostname}:7861/api`;
};
const API_BASE = getApiBase();
// 当前是否为编辑模式
let isEditMode = false;
let editToolId = null;
// 可选图标列表
function getIconOptions() {
return [
'fa-cog', 'fa-cogs', 'fa-database', 'fa-code', 'fa-file-text',
'fa-file-code', 'fa-file-excel', 'fa-file', 'fa-folder',
'fa-wrench', 'fa-tools', 'fa-magic', 'fa-puzzle-piece',
'fa-plug', 'fa-cube', 'fa-cubes', 'fa-gear', 'fa-sliders',
'fa-filter', 'fa-refresh', 'fa-exchange', 'fa-arrows-alt',
'fa-random', 'fa-random', 'fa-link', 'fa-chain',
'fa-edit', 'fa-pencil', 'fa-pen', 'fa-plus-circle',
'fa-star', 'fa-heart', 'fa-bookmark', 'fa-tag'
];
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 检查是否为编辑模式
const urlParams = new URLSearchParams(window.location.search);
isEditMode = urlParams.get('edit') === 'true';
// 渲染图标选择
renderIconGrid();
// 如果是编辑模式,加载工具数据
if (isEditMode) {
loadToolForEdit();
}
// 描述字数统计
const descInput = document.querySelector('textarea[name="description"]');
if (descInput) {
descInput.addEventListener('input', () => {
document.getElementById('descCount').textContent = descInput.value.length;
});
}
// 绑定导航点击事件
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', function(e) {
if (!this.href.includes('custom-tool-create')) {
e.preventDefault();
window.location.href = this.href;
}
});
});
// 设置侧边栏当前页高亮
const currentPage = 'data-generate';
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();
});
// 更新侧边栏滑块位置
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 loadToolForEdit() {
const editToolStr = localStorage.getItem('editTool');
if (editToolStr) {
const tool = JSON.parse(editToolStr);
editToolId = tool.id;
// 更新页面标题
document.getElementById('pageTitle').textContent = '修改自定义工具';
document.title = '修改自定义工具 / 远光软件微调平台';
// 填充表单
document.querySelector('input[name="name"]').value = tool.name || '';
document.querySelector('textarea[name="description"]').value = tool.description || '';
document.querySelector('input[name="url"]').value = tool.url || '';
// 选中图标
const icon = tool.icon || 'fa-cog';
setSelectedIcon(icon);
// 更新字数统计
document.getElementById('descCount').textContent = (tool.description || '').length;
}
}
// 设置选中的图标
function setSelectedIcon(iconClass) {
document.querySelectorAll('.icon-option').forEach(item => {
item.classList.remove('selected');
if (item.dataset.icon === iconClass) {
item.classList.add('selected');
}
});
document.querySelector('input[name="icon"]').value = iconClass;
}
// 渲染图标选择网格
function renderIconGrid() {
const iconGrid = document.getElementById('iconGrid');
const icons = getIconOptions();
iconGrid.innerHTML = icons.map((icon, idx) => `
<div class="icon-option ${idx === 0 ? 'selected' : ''}" data-icon="${icon}" onclick="selectIcon(this)">
<i class="fa ${icon} text-lg text-gray-600"></i>
</div>
`).join('');
}
// 选择图标
function selectIcon(el) {
document.querySelectorAll('.icon-option').forEach(item => {
item.classList.remove('selected');
});
el.classList.add('selected');
document.querySelector('input[name="icon"]').value = el.dataset.icon;
}
// 提交表单
function submitForm() {
const form = document.getElementById('toolForm');
const formData = new FormData(form);
const name = formData.get('name').trim();
const url = formData.get('url').trim();
const icon = formData.get('icon');
const description = formData.get('description').trim();
if (!name) {
showMessage('提示', '请输入工具名称', 'warning');
return;
}
if (!url) {
showMessage('提示', '请输入跳转地址', 'warning');
return;
}
if (isEditMode && editToolId) {
// 编辑模式:更新现有工具
updateCustomTool(editToolId, name, description, icon, url);
} else {
// 新增模式:创建新工具
const toolId = 'custom_' + Date.now();
const newTool = {
id: toolId,
name: name,
description: description || '自定义工具',
icon: icon,
url: url
};
saveCustomTool(newTool);
showMessage('成功', '自定义工具添加成功!', 'success', () => {
window.location.href = 'main.html?page=data-generate';
});
}
}
// 更新自定义工具
function updateCustomTool(toolId, name, description, icon, url) {
let customTools = JSON.parse(localStorage.getItem('customTools') || '[]');
const index = customTools.findIndex(t => t.id === toolId);
if (index !== -1) {
customTools[index] = {
...customTools[index],
name: name,
description: description || '自定义工具',
icon: icon,
url: url
};
localStorage.setItem('customTools', JSON.stringify(customTools));
// 清除编辑数据
localStorage.removeItem('editTool');
showMessage('成功', '自定义工具修改成功!', 'success', () => {
window.location.href = 'main.html?page=data-generate';
});
}
}
// 保存自定义工具
function saveCustomTool(tool) {
let customTools = JSON.parse(localStorage.getItem('customTools') || '[]');
customTools.push(tool);
localStorage.setItem('customTools', JSON.stringify(customTools));
}
// ============ 自定义消息弹窗 ============
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');
const modalBtnGroup = document.getElementById('modalBtnGroup');
const modalSingleBtnGroup = document.getElementById('modalSingleBtnGroup');
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>';
} 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>';
}
modalBtnGroup.classList.add('hidden');
modalSingleBtnGroup.classList.remove('hidden');
modalConfirmBtn.className = type === 'error' ? 'px-6 py-2 bg-danger text-white rounded-lg hover:bg-danger/90 transition-colors' : 'px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors';
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
modalConfirmBtn.onclick = () => {
modal.classList.add('hidden');
document.body.style.overflow = '';
if (onConfirm) onConfirm();
};
}
</script>
<!-- 自定义消息弹窗 -->
<div id="customModal" class="hidden fixed inset-0 bg-black/50 z-50 flex items-center justify-center">
<div class="bg-white rounded-xl shadow-xl max-w-md w-full mx-4 overflow-hidden">
<div class="p-6 text-center">
<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="modalBtnGroup" class="hidden px-6 pb-6">
<button id="modalConfirmBtn" class="px-4 py-2 bg-primary text-white rounded-lg w-full hover:bg-primary/90 transition-colors">
确定
</button>
</div>
<div id="modalSingleBtnGroup" class="px-6 pb-6 flex justify-center">
<button id="modalConfirmBtn2" class="px-6 py-2 text-white rounded-lg transition-colors">确定</button>
</div>
</div>
</div>
</body>
</html>