mirror of
https://github.com/jingyaogong/minimind.git
synced 2026-01-13 19:57:20 +08:00
update web dataset file
This commit is contained in:
parent
4a741b3d3b
commit
a0013eae8f
@ -366,6 +366,232 @@ input[type="text"]:focus, input[type="number"]:focus, select:focus, textarea:foc
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* 文件浏览器模态框样式 */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.modal.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--panel-bg);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: var(--shadow-xl);
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 1rem 1.5rem;
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
background: rgba(22, 22, 32, 0.8);
|
||||
}
|
||||
|
||||
.modal-footer input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, var(--border-light) 0%, var(--border) 100%);
|
||||
color: var(--text);
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
transform: translateY(-1px);
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
/* 文件浏览器导航 */
|
||||
.file-browser-nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem;
|
||||
background: rgba(45, 55, 72, 0.5);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.current-path {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
flex: 1;
|
||||
margin-right: 1rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: rgba(22, 22, 32, 0.5);
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.btn-navigate {
|
||||
background: linear-gradient(135deg, var(--info-grad-start) 0%, var(--info-grad-end) 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-navigate:hover {
|
||||
transform: translateY(-1px);
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
/* 快捷路径 */
|
||||
.quick-paths {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.quick-path-btn {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
color: var(--accent);
|
||||
border: 1px solid rgba(139, 92, 246, 0.3);
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.quick-path-btn:hover {
|
||||
background: rgba(139, 92, 246, 0.2);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 文件列表 */
|
||||
.file-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(22, 22, 32, 0.3);
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border-bottom: 1px solid rgba(45, 55, 72, 0.3);
|
||||
}
|
||||
|
||||
.file-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
}
|
||||
|
||||
.file-item.selected {
|
||||
background: rgba(139, 92, 246, 0.2);
|
||||
border-left: 3px solid var(--accent);
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
margin-right: 0.75rem;
|
||||
font-size: 1.2rem;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
flex: 1;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.file-info {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.directory {
|
||||
color: var(--info-grad-start);
|
||||
}
|
||||
|
||||
.file {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -17,37 +17,182 @@ 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 本地文件浏览器(兼容模式)
|
||||
async function openLocalFileBrowser(inputId) {
|
||||
try {
|
||||
// 检查是否支持 File System Access API
|
||||
if ('showDirectoryPicker' in window) {
|
||||
const dirHandle = await window.showDirectoryPicker();
|
||||
const path = dirHandle.name; // 使用目录名称作为路径
|
||||
const path = dirHandle.name;
|
||||
document.getElementById(inputId).value = `./${path}`;
|
||||
} else {
|
||||
// 降级方案:使用文件输入模拟
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.webkitdirectory = true;
|
||||
input.onchange = (e) => {
|
||||
const files = e.target.files;
|
||||
if (files.length > 0) {
|
||||
// 提取相对路径
|
||||
const path = files[0].webkitRelativePath.split('/')[0];
|
||||
document.getElementById(inputId).value = `./${path}`;
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
// 降级到远程文件浏览
|
||||
await openRemoteFileBrowser(inputId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('文件夹选择失败:', error);
|
||||
// 如果用户取消选择,不显示错误
|
||||
if (error.name !== 'AbortError') {
|
||||
alert('文件夹选择失败,请手动输入路径');
|
||||
}
|
||||
// 如果本地失败,降级到远程文件浏览
|
||||
await openRemoteFileBrowser(inputId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 远程文件浏览器
|
||||
let currentFileBrowserTarget = null;
|
||||
let currentBrowsePath = './';
|
||||
|
||||
function openRemoteFileBrowser(inputId) {
|
||||
currentFileBrowserTarget = inputId;
|
||||
document.getElementById('file-browser-modal').classList.remove('hidden');
|
||||
|
||||
// 加载初始路径
|
||||
loadQuickPaths();
|
||||
browsePath('./');
|
||||
}
|
||||
|
||||
function closeFileBrowser() {
|
||||
document.getElementById('file-browser-modal').classList.add('hidden');
|
||||
currentFileBrowserTarget = null;
|
||||
currentBrowsePath = './';
|
||||
}
|
||||
|
||||
function confirmFileSelection() {
|
||||
const selectedPath = document.getElementById('selected-path').value;
|
||||
if (selectedPath && currentFileBrowserTarget) {
|
||||
document.getElementById(currentFileBrowserTarget).value = selectedPath;
|
||||
}
|
||||
closeFileBrowser();
|
||||
}
|
||||
|
||||
function navigateToParent() {
|
||||
if (currentBrowsePath !== './') {
|
||||
const parentPath = currentBrowsePath.includes('/') ?
|
||||
currentBrowsePath.substring(0, currentBrowsePath.lastIndexOf('/')) : './';
|
||||
browsePath(parentPath || './');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadQuickPaths() {
|
||||
try {
|
||||
const response = await fetch('/api/quick-paths');
|
||||
const data = await response.json();
|
||||
|
||||
const quickPathsContainer = document.getElementById('quick-paths');
|
||||
quickPathsContainer.innerHTML = '';
|
||||
|
||||
if (data.paths && data.paths.length > 0) {
|
||||
data.paths.forEach(path => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'quick-path-btn';
|
||||
btn.textContent = path.name;
|
||||
btn.onclick = () => browsePath(path.path);
|
||||
btn.title = path.path;
|
||||
quickPathsContainer.appendChild(btn);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('加载快捷路径失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function browsePath(path) {
|
||||
try {
|
||||
currentBrowsePath = path;
|
||||
document.getElementById('current-path').textContent = path;
|
||||
|
||||
const response = await fetch(`/api/browse?path=${encodeURIComponent(path)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
alert(`浏览失败: ${data.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
renderFileList(data);
|
||||
} catch (error) {
|
||||
console.error('浏览路径失败:', error);
|
||||
alert('浏览路径失败,请检查网络连接');
|
||||
}
|
||||
}
|
||||
|
||||
function renderFileList(data) {
|
||||
const fileList = document.getElementById('file-list');
|
||||
fileList.innerHTML = '';
|
||||
|
||||
if (!data.items || data.items.length === 0) {
|
||||
fileList.innerHTML = '<div style="padding: 2rem; text-align: center; color: var(--text-secondary);">此目录为空</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 先显示目录,再显示文件
|
||||
const directories = data.items.filter(item => item.type === 'directory');
|
||||
const files = data.items.filter(item => item.type === 'file');
|
||||
|
||||
// 渲染目录
|
||||
directories.forEach(item => {
|
||||
const div = createFileItem(item, '📁');
|
||||
fileList.appendChild(div);
|
||||
});
|
||||
|
||||
// 渲染文件
|
||||
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);
|
||||
|
||||
div.innerHTML = `
|
||||
<span class="file-icon">${icon}</span>
|
||||
<span class="file-name">${item.name}</span>
|
||||
<span class="file-info">${item.type === 'file' ? formatFileSize(item.size) : '文件夹'}</span>
|
||||
`;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
function selectFileItem(item) {
|
||||
if (item.type === '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');
|
||||
}
|
||||
}
|
||||
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
initTrainForm();
|
||||
|
||||
@ -319,6 +319,33 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件浏览器模态框 -->
|
||||
<div id="file-browser-modal" class="modal hidden">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>选择文件夹</h3>
|
||||
<button class="modal-close" onclick="closeFileBrowser()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="file-browser-nav">
|
||||
<div class="current-path" id="current-path">./</div>
|
||||
<button class="btn-navigate" onclick="navigateToParent()">⬆️ 上级目录</button>
|
||||
</div>
|
||||
<div class="quick-paths" id="quick-paths">
|
||||
<!-- 快捷路径将在这里显示 -->
|
||||
</div>
|
||||
<div class="file-list" id="file-list">
|
||||
<!-- 文件列表将在这里显示 -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="text" id="selected-path" placeholder="选择的文件夹路径" readonly>
|
||||
<button class="btn-primary" onclick="confirmFileSelection()">确认选择</button>
|
||||
<button class="btn-secondary" onclick="closeFileBrowser()">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/static/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -11,6 +11,8 @@ from flask import Flask, render_template, request, jsonify, redirect, url_for
|
||||
from flask import g
|
||||
import time
|
||||
import psutil
|
||||
import glob
|
||||
import pathlib
|
||||
|
||||
# 尝试导入torch来检测GPU
|
||||
try:
|
||||
@ -320,6 +322,115 @@ def processes():
|
||||
})
|
||||
return jsonify(result)
|
||||
|
||||
@app.route('/api/browse')
|
||||
def browse_files():
|
||||
"""
|
||||
浏览服务器文件系统
|
||||
支持远程文件选择功能
|
||||
"""
|
||||
try:
|
||||
# 获取请求的路径参数
|
||||
path = request.args.get('path', './')
|
||||
|
||||
# 安全检查:限制访问范围
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.abspath(os.path.join(script_dir, '..'))
|
||||
|
||||
# 解析请求的路径
|
||||
if path.startswith('./'):
|
||||
# 相对路径,基于项目根目录
|
||||
full_path = os.path.abspath(os.path.join(project_root, path[2:]))
|
||||
elif path.startswith('/'):
|
||||
# 绝对路径,检查是否在项目目录内
|
||||
full_path = os.path.abspath(path)
|
||||
else:
|
||||
# 相对路径,基于项目根目录
|
||||
full_path = os.path.abspath(os.path.join(project_root, path))
|
||||
|
||||
# 安全检查:确保路径在项目目录内
|
||||
if not full_path.startswith(project_root):
|
||||
full_path = project_root
|
||||
|
||||
# 检查路径是否存在
|
||||
if not os.path.exists(full_path):
|
||||
return jsonify({'error': '路径不存在', 'path': path})
|
||||
|
||||
# 获取目录内容
|
||||
if os.path.isdir(full_path):
|
||||
items = []
|
||||
try:
|
||||
# 列出目录内容
|
||||
for item in sorted(os.listdir(full_path)):
|
||||
item_path = os.path.join(full_path, item)
|
||||
|
||||
# 跳过隐藏文件和系统文件
|
||||
if item.startswith('.') or item.startswith('__'):
|
||||
continue
|
||||
|
||||
try:
|
||||
stat = os.stat(item_path)
|
||||
items.append({
|
||||
'name': item,
|
||||
'path': os.path.relpath(item_path, project_root),
|
||||
'type': 'directory' if os.path.isdir(item_path) else 'file',
|
||||
'size': stat.st_size if os.path.isfile(item_path) else 0,
|
||||
'modified': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(stat.st_mtime))
|
||||
})
|
||||
except (OSError, PermissionError):
|
||||
# 跳过无法访问的项目
|
||||
continue
|
||||
|
||||
return jsonify({
|
||||
'current_path': os.path.relpath(full_path, project_root),
|
||||
'absolute_path': full_path,
|
||||
'items': items,
|
||||
'parent': os.path.relpath(os.path.dirname(full_path), project_root) if full_path != project_root else None
|
||||
})
|
||||
except (OSError, PermissionError) as e:
|
||||
return jsonify({'error': f'无法访问目录: {str(e)}', 'path': path})
|
||||
|
||||
else:
|
||||
# 如果是文件,返回文件信息
|
||||
stat = os.stat(full_path)
|
||||
return jsonify({
|
||||
'name': os.path.basename(full_path),
|
||||
'path': os.path.relpath(full_path, project_root),
|
||||
'type': 'file',
|
||||
'size': stat.st_size,
|
||||
'modified': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(stat.st_mtime))
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': f'浏览文件时出错: {str(e)}'})
|
||||
|
||||
@app.route('/api/quick-paths')
|
||||
def quick_paths():
|
||||
"""
|
||||
返回常用路径快捷方式
|
||||
"""
|
||||
try:
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.abspath(os.path.join(script_dir, '..'))
|
||||
|
||||
quick_paths = [
|
||||
{'name': '项目根目录', 'path': './', 'type': 'directory'},
|
||||
{'name': '数据集目录', 'path': './dataset', 'type': 'directory'},
|
||||
{'name': '模型检查点', 'path': './checkpoints', 'type': 'directory'},
|
||||
{'name': '日志文件', 'path': './logfile', 'type': 'directory'}
|
||||
]
|
||||
|
||||
# 验证路径是否存在
|
||||
valid_paths = []
|
||||
for item in quick_paths:
|
||||
full_path = os.path.join(project_root, item['path'][2:] if item['path'].startswith('./') else item['path'])
|
||||
if os.path.exists(full_path):
|
||||
valid_paths.append(item)
|
||||
|
||||
return jsonify({'paths': valid_paths})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': f'获取快捷路径时出错: {str(e)}'})
|
||||
|
||||
@app.route('/logs/<process_id>')
|
||||
def logs(process_id):
|
||||
# 直接从本地logfile目录读取日志文件
|
||||
|
||||
Loading…
Reference in New Issue
Block a user