From 249d1c08fec6511cabd087f24782132d76645c4f Mon Sep 17 00:00:00 2001 From: wangyuzhan Date: Fri, 21 Nov 2025 12:09:05 +0800 Subject: [PATCH] update web dataset file --- trainer_web/static/css/style.css | 59 ++++++++ trainer_web/static/js/app.js | 246 ++++++++++++++++++++++++------- trainer_web/templates/index.html | 12 +- 3 files changed, 258 insertions(+), 59 deletions(-) diff --git a/trainer_web/static/css/style.css b/trainer_web/static/css/style.css index 3a68d21..988ccc9 100644 --- a/trainer_web/static/css/style.css +++ b/trainer_web/static/css/style.css @@ -472,6 +472,37 @@ input[type="text"]:focus, input[type="number"]:focus, select:focus, textarea:foc filter: brightness(1.1); } +/* 模态框底部样式 */ +.modal-footer { + display: flex; + gap: 0.75rem; + align-items: center; + padding-top: 1rem; + border-top: 1px solid var(--border); +} + +.modal-footer input { + flex: 1; + margin-right: 0.5rem; +} + +/* 改进模态框关闭按钮 */ +.modal-close { + background: none; + border: none; + font-size: 1.5rem; + color: var(--text-secondary); + cursor: pointer; + padding: 0.25rem; + border-radius: var(--radius-sm); + transition: all 0.3s ease; +} + +.modal-close:hover { + color: var(--text); + background: rgba(239, 68, 68, 0.1); +} + /* 文件浏览器导航 */ .file-browser-nav { display: flex; @@ -483,6 +514,11 @@ input[type="text"]:focus, input[type="number"]:focus, select:focus, textarea:foc border-radius: var(--radius-md); } +.nav-buttons { + display: flex; + gap: 0.5rem; +} + .current-path { font-family: 'Courier New', monospace; font-size: 0.85rem; @@ -519,6 +555,19 @@ input[type="text"]:focus, input[type="number"]:focus, select:focus, textarea:foc flex-wrap: wrap; } +.file-browser-help { + background: rgba(59, 130, 246, 0.1); + border: 1px solid rgba(59, 130, 246, 0.3); + border-radius: var(--radius-sm); + padding: 0.75rem; + margin-bottom: 1rem; + font-size: 0.8rem; + color: var(--info-grad-start); + display: flex; + align-items: center; + gap: 0.5rem; +} + .quick-path-btn { background: rgba(139, 92, 246, 0.1); color: var(--accent); @@ -542,6 +591,7 @@ input[type="text"]:focus, input[type="number"]:focus, select:focus, textarea:foc border: 1px solid var(--border); border-radius: var(--radius-md); background: rgba(22, 22, 32, 0.3); + margin-bottom: 1rem; } .file-item { @@ -566,6 +616,15 @@ input[type="text"]:focus, input[type="number"]:focus, select:focus, textarea:foc border-left: 3px solid var(--accent); } +.file-item.disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.file-item.disabled:hover { + background: none; +} + .file-icon { margin-right: 0.75rem; font-size: 1.2rem; diff --git a/trainer_web/static/js/app.js b/trainer_web/static/js/app.js index 1fae11d..aa5d778 100644 --- a/trainer_web/static/js/app.js +++ b/trainer_web/static/js/app.js @@ -17,53 +17,50 @@ const hooks = { window.openTab = (evt, tabName) => _openTab(evt, tabName, hooks); -// 文件夹选择器功能 - 支持远程文件浏览 -window.selectFolder = async (inputId) => { - try { - // 检测是否为远程连接(通过检查主机名或端口) - const isRemote = window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1'; - - if (isRemote) { - // 远程连接:使用服务器端文件浏览 - openRemoteFileBrowser(inputId); - } else { - // 本地连接:尝试本地文件系统访问 - await openLocalFileBrowser(inputId); - } - } catch (error) { - console.warn('文件夹选择失败:', error); - if (error.name !== 'AbortError') { - // 降级到远程文件浏览 - openRemoteFileBrowser(inputId); - } - } +// 文件夹选择器功能 - 直接显示服务器端文件浏览器 +window.selectFolder = (inputId) => { + // 直接使用远程文件浏览器,不尝试本地文件系统访问 + openRemoteFileBrowser(inputId); }; -// 本地文件浏览器(兼容模式) -async function openLocalFileBrowser(inputId) { - try { - // 检查是否支持 File System Access API - if ('showDirectoryPicker' in window) { - const dirHandle = await window.showDirectoryPicker(); - const path = dirHandle.name; - document.getElementById(inputId).value = `./${path}`; - } else { - // 降级到远程文件浏览 - await openRemoteFileBrowser(inputId); - } - } catch (error) { - // 如果本地失败,降级到远程文件浏览 - await openRemoteFileBrowser(inputId); - } -} - -// 远程文件浏览器 +// 远程文件浏览器 - 支持文件和文件夹选择 let currentFileBrowserTarget = null; let currentBrowsePath = './'; +let selectedFilePath = null; +let currentSelectionMode = 'auto'; // 'file', 'folder', or 'auto' function openRemoteFileBrowser(inputId) { + console.log('openRemoteFileBrowser called with:', inputId); currentFileBrowserTarget = inputId; - document.getElementById('file-browser-modal').classList.remove('hidden'); + + // 根据输入框ID确定选择模式 + if (inputId === 'data_path') { + currentSelectionMode = 'file'; // 数据路径需要文件选择 + console.log('Mode set to: FILE selection'); + } else if (inputId === 'save_dir' || inputId.includes('reward_model_path')) { + currentSelectionMode = 'folder'; // 保存目录和奖励模型路径需要文件夹选择 + console.log('Mode set to: FOLDER selection'); + } else { + currentSelectionMode = 'auto'; // 自动模式 + console.log('Mode set to: AUTO selection'); + } + + const modal = document.getElementById('file-browser-modal'); + if (modal) { + modal.classList.remove('hidden'); + console.log('Modal opened successfully'); + } else { + console.error('Modal element not found!'); + return; + } + + // 重置选择状态 + selectedFilePath = null; + const selectedPathInput = document.getElementById('selected-path'); + if (selectedPathInput) { + selectedPathInput.value = ''; + console.log('Selected path input cleared'); + } // 加载初始路径 loadQuickPaths(); @@ -74,14 +71,31 @@ function closeFileBrowser() { document.getElementById('file-browser-modal').classList.add('hidden'); currentFileBrowserTarget = null; currentBrowsePath = './'; + selectedFilePath = null; + currentSelectionMode = 'auto'; } function confirmFileSelection() { - const selectedPath = document.getElementById('selected-path').value; - if (selectedPath && currentFileBrowserTarget) { - document.getElementById(currentFileBrowserTarget).value = selectedPath; + console.log('confirmFileSelection called'); + console.log('selectedFilePath:', selectedFilePath); + console.log('currentFileBrowserTarget:', currentFileBrowserTarget); + + if (selectedFilePath && currentFileBrowserTarget) { + const targetElement = document.getElementById(currentFileBrowserTarget); + console.log('targetElement:', targetElement); + + if (targetElement) { + targetElement.value = selectedFilePath; + console.log('Value set successfully'); + closeFileBrowser(); + } else { + console.error('Target element not found:', currentFileBrowserTarget); + alert('错误:无法找到目标输入框'); + } + } else { + console.log('Missing selection or target'); + alert('请先选择文件或文件夹'); } - closeFileBrowser(); } function navigateToParent() { @@ -92,6 +106,13 @@ function navigateToParent() { } } +function selectCurrentDirectory() { + // 选择当前目录 + selectedFilePath = currentBrowsePath; + document.getElementById('selected-path').value = currentBrowsePath; + // 可以关闭模态框或让用户继续浏览 +} + async function loadQuickPaths() { try { const response = await fetch('/api/quick-paths'); @@ -116,9 +137,15 @@ async function loadQuickPaths() { } async function browsePath(path) { + console.log('browsePath called with:', path); try { currentBrowsePath = path; + selectedFilePath = null; // 重置选中的文件路径 document.getElementById('current-path').textContent = path; + document.getElementById('selected-path').value = ''; // 清空显示 + + // 更新帮助文本 + updateHelpText(); const response = await fetch(`/api/browse?path=${encodeURIComponent(path)}`); const data = await response.json(); @@ -129,6 +156,7 @@ async function browsePath(path) { } renderFileList(data); + console.log('File list rendered successfully'); } catch (error) { console.error('浏览路径失败:', error); alert('浏览路径失败,请检查网络连接'); @@ -154,17 +182,29 @@ function renderFileList(data) { fileList.appendChild(div); }); - // 渲染文件 - files.forEach(item => { - const div = createFileItem(item, '📄'); - fileList.appendChild(div); - }); + // 渲染文件(仅在文件选择模式或自动模式下显示) + if (currentSelectionMode !== 'folder') { + files.forEach(item => { + const div = createFileItem(item, '📄'); + fileList.appendChild(div); + }); + } } function createFileItem(item, icon) { const div = document.createElement('div'); div.className = 'file-item'; - div.onclick = () => selectFileItem(item); + + // 根据选择模式添加适当的CSS类 + if (currentSelectionMode === 'file' && item.type === 'directory') { + // 文件选择模式下,文件夹只用于导航,不能选择 + div.classList.add('navigable'); + } else if (currentSelectionMode === 'folder' && item.type === 'file') { + // 文件夹选择模式下,文件不能被选择 + div.classList.add('disabled'); + } + + div.onclick = (event) => selectFileItem(item, event); div.innerHTML = ` ${icon} @@ -175,14 +215,54 @@ function createFileItem(item, icon) { return div; } -function selectFileItem(item) { +function selectFileItem(item, event) { + console.log('selectFileItem called with:', item); + console.log('currentSelectionMode:', currentSelectionMode); + console.log('event:', event); + + // 检查是否点击了被禁用的项目 + if (event && event.currentTarget && event.currentTarget.classList.contains('disabled')) { + console.log('Clicked disabled item, ignoring'); + return; + } + if (item.type === 'directory') { - browsePath(item.path); + // 文件夹:根据选择模式决定行为 + if (currentSelectionMode === 'file') { + // 文件选择模式:只能选择文件,点击进入目录 + console.log('File mode: navigating into directory'); + browsePath(item.path); + } else if (currentSelectionMode === 'folder') { + // 文件夹选择模式:可以选择文件夹 + console.log('Folder mode: selecting directory'); + selectedFilePath = item.path; + document.getElementById('selected-path').value = item.path; + // 高亮显示选中的文件夹 + document.querySelectorAll('.file-item').forEach(el => el.classList.remove('selected')); + if (event && event.currentTarget) { + event.currentTarget.classList.add('selected'); + } + console.log('Directory selected:', selectedFilePath); + } else { + // 自动模式:点击进入目录 + console.log('Auto mode: navigating into directory'); + browsePath(item.path); + } } else { - document.getElementById('selected-path').value = item.path; - // 文件被选中,可以高亮显示 - document.querySelectorAll('.file-item').forEach(el => el.classList.remove('selected')); - event.currentTarget.classList.add('selected'); + // 文件:选中文件路径(仅在选择文件或自动模式下) + if (currentSelectionMode !== 'folder') { + console.log('Selecting file:', item.path); + selectedFilePath = item.path; + document.getElementById('selected-path').value = item.path; + // 高亮显示选中的文件 + document.querySelectorAll('.file-item').forEach(el => el.classList.remove('selected')); + if (event && event.currentTarget) { + event.currentTarget.classList.add('selected'); + } + console.log('File selected:', selectedFilePath); + } else { + console.log('File clicked in folder mode, ignoring'); + } } } @@ -194,6 +274,60 @@ function formatFileSize(bytes) { return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } +function updateHelpText() { + const helpText = document.querySelector('.file-browser-help'); + const modalTitle = document.getElementById('modal-title'); + + if (!helpText) return; + + let text = ''; + let title = ''; + switch (currentSelectionMode) { + case 'file': + text = '💡 请选择文件:点击文件选择,点击文件夹进入目录,使用📍选择当前目录'; + title = '选择文件'; + break; + case 'folder': + text = '💡 请选择文件夹:点击文件夹选择,点击文件无效,使用📍选择当前目录'; + title = '选择文件夹'; + break; + default: + text = '💡 点击文件夹进入目录,点击文件选择文件,使用📍选择当前目录'; + title = '选择文件或文件夹'; + } + helpText.textContent = text; + if (modalTitle) { + modalTitle.textContent = title; + } +} + +// 添加模态框键盘事件监听 +document.addEventListener('keydown', function(event) { + if (event.key === 'Escape') { + closeFileBrowser(); + } +}); + +// 添加模态框点击外部关闭功能 +document.addEventListener('DOMContentLoaded', function() { + const modal = document.getElementById('file-browser-modal'); + if (modal) { + modal.addEventListener('click', function(event) { + if (event.target === modal) { + closeFileBrowser(); + } + }); + } +}); + +// 将文件浏览器函数暴露到全局作用域 +window.selectFolder = selectFolder; +window.openRemoteFileBrowser = openRemoteFileBrowser; +window.closeFileBrowser = closeFileBrowser; +window.confirmFileSelection = confirmFileSelection; +window.navigateToParent = navigateToParent; +window.selectCurrentDirectory = selectCurrentDirectory; + window.addEventListener('load', () => { initTrainForm(); startProcessPolling(); diff --git a/trainer_web/templates/index.html b/trainer_web/templates/index.html index 4390001..701611e 100644 --- a/trainer_web/templates/index.html +++ b/trainer_web/templates/index.html @@ -323,23 +323,29 @@