|
|
|
|
@ -1,565 +0,0 @@
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<title>基于对抗性扰动的多风格图像生成防护系统 - 测试页面</title>
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
|
|
|
|
<style>
|
|
|
|
|
body { padding-top: 70px; }
|
|
|
|
|
#raw-data { max-height: 300px; overflow: auto; background: #f8f9fa; border: 1px solid #ddd; padding: 10px; }
|
|
|
|
|
.nav-link.active { font-weight: bold; }
|
|
|
|
|
.pointer { cursor: pointer; }
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
|
|
|
|
|
<div class="container-fluid">
|
|
|
|
|
<a class="navbar-brand" href="#" onclick="gotoPage('demo')">图像防护系统</a>
|
|
|
|
|
<div class="collapse navbar-collapse">
|
|
|
|
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
|
|
|
|
<li class="nav-item"><a class="nav-link" id="nav-demo" href="#" onclick="gotoPage('demo')">Demo展示</a></li>
|
|
|
|
|
<li class="nav-item"><a class="nav-link" id="nav-auth" href="#" onclick="gotoPage('auth')">认证</a></li>
|
|
|
|
|
<li class="nav-item"><a class="nav-link" id="nav-tasks" href="#" onclick="gotoPage('tasks')">扰动任务</a></li>
|
|
|
|
|
<li class="nav-item"><a class="nav-link" id="nav-finetune" href="#" onclick="gotoPage('finetune')">微调任务</a></li>
|
|
|
|
|
<li class="nav-item"><a class="nav-link" id="nav-create" href="#" onclick="gotoPage('create')">创建任务</a></li>
|
|
|
|
|
<li class="nav-item"><a class="nav-link" id="nav-profile" href="#" onclick="gotoPage('profile')">个人信息</a></li>
|
|
|
|
|
</ul>
|
|
|
|
|
<span class="navbar-text" id="user-info"></span>
|
|
|
|
|
<button class="btn btn-outline-light ms-2 d-none" id="logout-btn" onclick="logout()">登出</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</nav>
|
|
|
|
|
<div class="container">
|
|
|
|
|
<!-- 页面内容区 -->
|
|
|
|
|
<div id="page-demo" class="page" style="display:none"></div>
|
|
|
|
|
<div id="page-auth" class="page" style="display:none"></div>
|
|
|
|
|
<div id="page-tasks" class="page" style="display:none"></div>
|
|
|
|
|
<div id="page-finetune" class="page" style="display:none"></div>
|
|
|
|
|
<div id="page-task-detail" class="page" style="display:none"></div>
|
|
|
|
|
<div id="page-finetune-detail" class="page" style="display:none"></div>
|
|
|
|
|
<div id="page-create" class="page" style="display:none"></div>
|
|
|
|
|
<div id="page-profile" class="page" style="display:none"></div>
|
|
|
|
|
<hr>
|
|
|
|
|
<h5>后端原始返回数据</h5>
|
|
|
|
|
<pre id="raw-data"></pre>
|
|
|
|
|
</div>
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
|
|
|
|
<script>
|
|
|
|
|
// 全局状态
|
|
|
|
|
let accessToken = localStorage.getItem('access_token') || '';
|
|
|
|
|
let currentUser = null;
|
|
|
|
|
let lastConfig = null;
|
|
|
|
|
|
|
|
|
|
function setAuthHeader() {
|
|
|
|
|
if (accessToken) {
|
|
|
|
|
axios.defaults.headers.common['Authorization'] = 'Bearer ' + accessToken;
|
|
|
|
|
} else {
|
|
|
|
|
delete axios.defaults.headers.common['Authorization'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
setAuthHeader();
|
|
|
|
|
|
|
|
|
|
function showRawData(data) {
|
|
|
|
|
document.getElementById('raw-data').textContent = JSON.stringify(data, null, 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function gotoPage(page, arg) {
|
|
|
|
|
document.querySelectorAll('.page').forEach(p => p.style.display = 'none');
|
|
|
|
|
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
|
|
|
|
|
if (page === 'demo') {
|
|
|
|
|
document.getElementById('nav-demo').classList.add('active');
|
|
|
|
|
loadDemoPage();
|
|
|
|
|
} else if (page === 'auth') {
|
|
|
|
|
document.getElementById('nav-auth').classList.add('active');
|
|
|
|
|
loadAuthPage();
|
|
|
|
|
} else if (page === 'tasks') {
|
|
|
|
|
document.getElementById('nav-tasks').classList.add('active');
|
|
|
|
|
loadTasksPage();
|
|
|
|
|
} else if (page === 'finetune') {
|
|
|
|
|
document.getElementById('nav-finetune').classList.add('active');
|
|
|
|
|
loadFinetunePage();
|
|
|
|
|
} else if (page === 'task-detail') {
|
|
|
|
|
loadTaskDetailPage(arg);
|
|
|
|
|
} else if (page === 'finetune-detail') {
|
|
|
|
|
loadFinetuneDetailPage(arg);
|
|
|
|
|
} else if (page === 'create') {
|
|
|
|
|
document.getElementById('nav-create').classList.add('active');
|
|
|
|
|
loadCreateTaskPage();
|
|
|
|
|
} else if (page === 'profile') {
|
|
|
|
|
document.getElementById('nav-profile').classList.add('active');
|
|
|
|
|
loadProfilePage();
|
|
|
|
|
}
|
|
|
|
|
document.getElementById('page-' + page).style.display = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Demo展示页
|
|
|
|
|
function loadDemoPage() {
|
|
|
|
|
const el = document.getElementById('page-demo');
|
|
|
|
|
el.innerHTML = '<h3>Demo展示</h3><div id="demo-algorithms" class="mb-3"></div><div id="demo-list"></div>';
|
|
|
|
|
// 获取算法信息
|
|
|
|
|
axios.get('/api/demo/algorithms').then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
const perturbs = res.data.perturbation_algorithms || [];
|
|
|
|
|
const finetunes = res.data.finetune_algorithms || [];
|
|
|
|
|
let html = '<h5>扰动算法</h5><ul>' + perturbs.map(a=>`<li><b>${a.name}</b> (${a.code}) - ${a.description}${a.default_epsilon!==null?`,默认扰动强度:${a.default_epsilon}`:''}</li>`).join('') + '</ul>';
|
|
|
|
|
html += '<h5>微调算法</h5><ul>' + finetunes.map(a=>`<li><b>${a.name}</b> (${a.code}) - ${a.description}</li>`).join('') + '</ul>';
|
|
|
|
|
html += '<h5>评估指标</h5><ul>' + (res.data.evaluation_metrics||[]).map(m=>`<li><b>${m.name}</b> - ${m.description}</li>`).join('') + '</ul>';
|
|
|
|
|
document.getElementById('demo-algorithms').innerHTML = html;
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
// 获取演示图片
|
|
|
|
|
axios.get('/api/demo/images').then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
const list = res.data.demo_images.map(img => `
|
|
|
|
|
<div class="card mb-2">
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<h5>${img.name}</h5>
|
|
|
|
|
<img src="${img.original}" style="max-width:120px;max-height:80px;" class="me-2">
|
|
|
|
|
${img.perturbed.map(p=>`<img src="${p}" style="max-width:120px;max-height:80px;" class="me-2">`).join('')}
|
|
|
|
|
${img.comparisons.map(c=>`<img src="${c}" style="max-width:120px;max-height:80px;" class="me-2">`).join('')}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`).join('');
|
|
|
|
|
document.getElementById('demo-list').innerHTML = list || '暂无演示图片';
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 认证页
|
|
|
|
|
function loadAuthPage() {
|
|
|
|
|
const el = document.getElementById('page-auth');
|
|
|
|
|
el.innerHTML = `
|
|
|
|
|
<h3>用户认证</h3>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-md-6">
|
|
|
|
|
<h5>登录</h5>
|
|
|
|
|
<form id="login-form">
|
|
|
|
|
<div class="mb-2"><input class="form-control" name="username" placeholder="用户名"></div>
|
|
|
|
|
<div class="mb-2"><input class="form-control" name="password" type="password" placeholder="密码"></div>
|
|
|
|
|
<button class="btn btn-primary w-100" type="submit">登录</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-md-6">
|
|
|
|
|
<h5>注册</h5>
|
|
|
|
|
<form id="register-form">
|
|
|
|
|
<div class="mb-2"><input class="form-control" name="username" placeholder="用户名"></div>
|
|
|
|
|
<div class="mb-2"><input class="form-control" name="password" type="password" placeholder="密码"></div>
|
|
|
|
|
<div class="mb-2"><input class="form-control" name="email" placeholder="邮箱"></div>
|
|
|
|
|
<button class="btn btn-success w-100" type="submit">注册</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
document.getElementById('login-form').onsubmit = function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const data = Object.fromEntries(new FormData(this));
|
|
|
|
|
axios.post('/api/auth/login', data).then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
accessToken = res.data.access_token;
|
|
|
|
|
localStorage.setItem('access_token', accessToken);
|
|
|
|
|
setAuthHeader();
|
|
|
|
|
getCurrentUser();
|
|
|
|
|
gotoPage('tasks');
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
};
|
|
|
|
|
document.getElementById('register-form').onsubmit = function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const data = Object.fromEntries(new FormData(this));
|
|
|
|
|
axios.post('/api/auth/register', data).then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
alert('注册成功,请登录');
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getCurrentUser() {
|
|
|
|
|
axios.get('/api/auth/profile').then(res => {
|
|
|
|
|
currentUser = res.data.user;
|
|
|
|
|
document.getElementById('user-info').textContent = currentUser.username;
|
|
|
|
|
document.getElementById('logout-btn').classList.remove('d-none');
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
currentUser = null;
|
|
|
|
|
document.getElementById('user-info').textContent = '';
|
|
|
|
|
document.getElementById('logout-btn').classList.add('d-none');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function logout() {
|
|
|
|
|
accessToken = '';
|
|
|
|
|
localStorage.removeItem('access_token');
|
|
|
|
|
setAuthHeader();
|
|
|
|
|
currentUser = null;
|
|
|
|
|
gotoPage('auth');
|
|
|
|
|
getCurrentUser();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 任务管理页(扰动任务)
|
|
|
|
|
function loadTasksPage() {
|
|
|
|
|
const el = document.getElementById('page-tasks');
|
|
|
|
|
el.innerHTML = '<h3>扰动任务管理</h3><div id="tasks-list"></div><button class="btn btn-success mt-2" onclick="gotoPage(\'create\')">创建新任务</button>';
|
|
|
|
|
axios.get('/api/task/list').then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
const list = res.data.tasks.map(task => `
|
|
|
|
|
<div class="card mb-2 pointer" onclick="gotoPage('task-detail',${task.id})">
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<b>${task.batch_name}</b> | 状态: <span class="badge bg-${getStatusColor(task.status)}">${task.status}</span> | 创建: ${task.created_at}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`).join('');
|
|
|
|
|
document.getElementById('tasks-list').innerHTML = list || '暂无任务';
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 微调任务管理页
|
|
|
|
|
function loadFinetunePage() {
|
|
|
|
|
const el = document.getElementById('page-finetune');
|
|
|
|
|
el.innerHTML = '<h3>微调任务管理</h3><div id="finetune-list"></div>';
|
|
|
|
|
axios.get('/api/task/finetune/list').then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
const list = res.data.finetune_tasks.map(ft => `
|
|
|
|
|
<div class="card mb-2 pointer" onclick="gotoPage('finetune-detail',${ft.id})">
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<b>微调任务 #${ft.id}</b> | 扰动任务: ${ft.batch_info?.batch_name || '未知'} |
|
|
|
|
|
状态: <span class="badge bg-${getStatusColor(ft.status)}">${ft.status}</span> |
|
|
|
|
|
方法: ${ft.finetune_config || '未设置'} | 创建: ${ft.created_at}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`).join('');
|
|
|
|
|
document.getElementById('finetune-list').innerHTML = list || '暂无微调任务';
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getStatusColor(status) {
|
|
|
|
|
const colors = {
|
|
|
|
|
'pending': 'secondary',
|
|
|
|
|
'queued': 'info',
|
|
|
|
|
'processing': 'warning',
|
|
|
|
|
'completed': 'success',
|
|
|
|
|
'failed': 'danger'
|
|
|
|
|
};
|
|
|
|
|
return colors[status] || 'secondary';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 任务详情页(扰动任务)
|
|
|
|
|
function loadTaskDetailPage(taskId) {
|
|
|
|
|
const el = document.getElementById('page-task-detail');
|
|
|
|
|
el.innerHTML = '<h3>扰动任务详情</h3><div id="task-detail-content"></div>';
|
|
|
|
|
axios.get(`/api/task/${taskId}`).then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
const task = res.data.task;
|
|
|
|
|
const images = res.data.images || [];
|
|
|
|
|
let actions = '';
|
|
|
|
|
if (task.status === 'pending') {
|
|
|
|
|
actions += `<button class='btn btn-primary' onclick='startTask(${task.id})'>开始扰动处理</button>`;
|
|
|
|
|
} else if (task.status === 'failed') {
|
|
|
|
|
actions += `<button class='btn btn-warning' onclick='restartTask(${task.id})'>重新开始</button>`;
|
|
|
|
|
} else if (task.status === 'completed') {
|
|
|
|
|
actions += `<button class='btn btn-success me-2' onclick='downloadImages(${task.id})'>下载扰动图片</button>`;
|
|
|
|
|
actions += `<button class='btn btn-info' onclick='viewFinetuneTask(${task.id})'>查看微调任务</button>`;
|
|
|
|
|
}
|
|
|
|
|
let imgs = images.map(img => `<img src='/api/image/file/${img.id}' style='max-width:120px;max-height:80px;' class='me-2 mb-2' title='${img.original_filename}'>`).join('');
|
|
|
|
|
document.getElementById('task-detail-content').innerHTML = `
|
|
|
|
|
<div class="mb-2"><b>任务名:</b> ${task.batch_name}</div>
|
|
|
|
|
<div class="mb-2"><b>状态:</b> <span class="badge bg-${getStatusColor(task.status)}">${task.status}</span></div>
|
|
|
|
|
<div class="mb-2"><b>扰动算法:</b> ${task.perturbation_config_id}</div>
|
|
|
|
|
<div class="mb-2"><b>扰动强度:</b> ${task.preferred_epsilon}</div>
|
|
|
|
|
<div class="mb-2"><b>强防护:</b> ${task.use_strong_protection ? '是' : '否'}</div>
|
|
|
|
|
<div class="mb-2"><b>创建时间:</b> ${task.created_at}</div>
|
|
|
|
|
${task.error_message ? `<div class="alert alert-danger">错误: ${task.error_message}</div>` : ''}
|
|
|
|
|
<div class='mb-3'>${actions}</div>
|
|
|
|
|
<h5>图片 (${images.length})</h5>
|
|
|
|
|
<div>${imgs || '暂无图片'}</div>
|
|
|
|
|
<button class='btn btn-secondary mt-3' onclick='gotoPage("tasks")'>返回任务列表</button>
|
|
|
|
|
`;
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 微调任务详情页
|
|
|
|
|
function loadFinetuneDetailPage(finetuneId) {
|
|
|
|
|
const el = document.getElementById('page-finetune-detail');
|
|
|
|
|
el.innerHTML = '<h3>微调任务详情</h3><div id="finetune-detail-content"></div>';
|
|
|
|
|
axios.get(`/api/task/finetune/${finetuneId}`).then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
const ft = res.data.finetune_task;
|
|
|
|
|
const batch = ft.batch_info;
|
|
|
|
|
let actions = '';
|
|
|
|
|
|
|
|
|
|
// 根据微调任务状态显示不同的操作按钮
|
|
|
|
|
if (ft.status === 'pending') {
|
|
|
|
|
// 待处理状态:检查是否已配置微调方法
|
|
|
|
|
if (!ft.finetune_config_id) {
|
|
|
|
|
// 未配置:显示配置按钮
|
|
|
|
|
actions += `<button class='btn btn-warning me-2' onclick='configureFinetuneTask(${ft.id})'>配置微调方法</button>`;
|
|
|
|
|
actions += `<div class="alert alert-warning mt-2">请先配置微调方法</div>`;
|
|
|
|
|
} else {
|
|
|
|
|
// 已配置:检查扰动任务是否完成
|
|
|
|
|
if (batch && batch.status === 'completed') {
|
|
|
|
|
// 扰动任务已完成:可以启动微调
|
|
|
|
|
actions += `<button class='btn btn-primary me-2' onclick='startFinetuneTask(${ft.id})'>开始微调</button>`;
|
|
|
|
|
actions += `<button class='btn btn-secondary' onclick='configureFinetuneTask(${ft.id})'>重新配置</button>`;
|
|
|
|
|
} else {
|
|
|
|
|
// 扰动任务未完成
|
|
|
|
|
actions += `<button class='btn btn-secondary me-2' onclick='configureFinetuneTask(${ft.id})'>重新配置</button>`;
|
|
|
|
|
actions += `<div class="alert alert-info mt-2">请等待扰动任务完成后再启动微调(当前扰动任务状态: ${batch?.status || '未知'})</div>`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (ft.status === 'failed') {
|
|
|
|
|
// 失败状态:允许重新开始或重新配置
|
|
|
|
|
actions += `<button class='btn btn-warning me-2' onclick='startFinetuneTask(${ft.id})'>重新开始微调</button>`;
|
|
|
|
|
actions += `<button class='btn btn-secondary' onclick='configureFinetuneTask(${ft.id})'>重新配置</button>`;
|
|
|
|
|
} else if (ft.status === 'completed') {
|
|
|
|
|
// 完成状态:显示下载按钮
|
|
|
|
|
actions += `<button class='btn btn-success' onclick='downloadFinetuneResults(${ft.id})'>下载微调结果</button>`;
|
|
|
|
|
} else if (ft.status === 'processing' || ft.status === 'queued') {
|
|
|
|
|
// 处理中或排队中:显示刷新按钮
|
|
|
|
|
actions += `<button class='btn btn-info' onclick='checkFinetuneStatus(${ft.id})'>刷新状态</button>`;
|
|
|
|
|
actions += `<div class="alert alert-info mt-2">微调任务正在处理中,请稍候...</div>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById('finetune-detail-content').innerHTML = `
|
|
|
|
|
<div class="mb-2"><b>微调任务 ID:</b> ${ft.id}</div>
|
|
|
|
|
<div class="mb-2"><b>状态:</b> <span class="badge bg-${getStatusColor(ft.status)}">${ft.status}</span></div>
|
|
|
|
|
<div class="mb-2"><b>关联扰动任务:</b> <a href="#" onclick="gotoPage('task-detail',${ft.batch_id}); event.preventDefault();">${batch?.batch_name || '未知'} (ID: ${ft.batch_id})</a></div>
|
|
|
|
|
<div class="mb-2"><b>扰动任务状态:</b> <span class="badge bg-${getStatusColor(batch?.status || 'unknown')}">${batch?.status || '未知'}</span></div>
|
|
|
|
|
<div class="mb-2"><b>微调方法:</b> ${ft.finetune_config || '<span class="text-muted">未设置</span>'}</div>
|
|
|
|
|
<div class="mb-2"><b>创建时间:</b> ${ft.created_at}</div>
|
|
|
|
|
${ft.started_at ? `<div class="mb-2"><b>开始时间:</b> ${ft.started_at}</div>` : ''}
|
|
|
|
|
${ft.completed_at ? `<div class="mb-2"><b>完成时间:</b> ${ft.completed_at}</div>` : ''}
|
|
|
|
|
${ft.error_message ? `<div class="alert alert-danger mt-2"><b>错误信息:</b> ${ft.error_message}</div>` : ''}
|
|
|
|
|
<hr>
|
|
|
|
|
<div class='mb-3'>${actions}</div>
|
|
|
|
|
<button class='btn btn-secondary' onclick='gotoPage("finetune")'>返回微调任务列表</button>
|
|
|
|
|
`;
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
}
|
|
|
|
|
function startTask(id) {
|
|
|
|
|
axios.post(`/api/task/start/${id}`).then(res=>{
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
alert('扰动任务已开始处理');
|
|
|
|
|
gotoPage('task-detail', id);
|
|
|
|
|
}).catch(e=>showRawData(e.response?.data||e));
|
|
|
|
|
}
|
|
|
|
|
function restartTask(id) {
|
|
|
|
|
axios.post(`/api/task/start/${id}`).then(res=>{
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
alert('任务已重新开始');
|
|
|
|
|
gotoPage('task-detail', id);
|
|
|
|
|
}).catch(e=>showRawData(e.response?.data||e));
|
|
|
|
|
}
|
|
|
|
|
function viewFinetuneTask(batchId) {
|
|
|
|
|
// 根据扰动任务ID查找微调任务
|
|
|
|
|
axios.get(`/api/task/finetune/by-batch/${batchId}`).then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
gotoPage('finetune-detail', res.data.finetune_task.id);
|
|
|
|
|
}).catch(e => {
|
|
|
|
|
showRawData(e.response?.data || e);
|
|
|
|
|
alert('未找到关联的微调任务');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
function configureFinetuneTask(finetuneId) {
|
|
|
|
|
// 获取可用的微调配置
|
|
|
|
|
axios.get('/api/task/finetune/configs').then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
const configs = res.data.configs;
|
|
|
|
|
const configOptions = configs.map(c => `<option value="${c.id}">${c.method_name} - ${c.description}</option>`).join('');
|
|
|
|
|
const html = `
|
|
|
|
|
<h5>配置微调方法</h5>
|
|
|
|
|
<form id="config-finetune-form">
|
|
|
|
|
<div class="mb-2">
|
|
|
|
|
<label>选择微调方法</label>
|
|
|
|
|
<select class="form-select" name="finetune_config_id">
|
|
|
|
|
${configOptions}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="btn btn-primary" type="submit">保存配置</button>
|
|
|
|
|
<button class="btn btn-secondary ms-2" type="button" onclick="gotoPage('finetune-detail',${finetuneId})">取消</button>
|
|
|
|
|
</form>
|
|
|
|
|
`;
|
|
|
|
|
document.getElementById('finetune-detail-content').innerHTML = html;
|
|
|
|
|
document.getElementById('config-finetune-form').onsubmit = function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const data = Object.fromEntries(new FormData(this));
|
|
|
|
|
axios.put(`/api/task/finetune/${finetuneId}/config`, data).then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
alert('微调配置已保存');
|
|
|
|
|
gotoPage('finetune-detail', finetuneId);
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
};
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
}
|
|
|
|
|
function startFinetuneTask(finetuneId) {
|
|
|
|
|
if (confirm('确定要开始微调任务吗?此过程可能需要较长时间。')) {
|
|
|
|
|
axios.post(`/api/task/finetune/${finetuneId}/start`).then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
alert('微调任务已开始');
|
|
|
|
|
gotoPage('finetune-detail', finetuneId);
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function checkFinetuneStatus(finetuneId) {
|
|
|
|
|
axios.get(`/api/task/finetune/${finetuneId}/status`).then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
alert(`微调任务状态: ${res.data.status}`);
|
|
|
|
|
gotoPage('finetune-detail', finetuneId);
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
}
|
|
|
|
|
function downloadFinetuneResults(finetuneId) {
|
|
|
|
|
alert('微调结果下载功能开发中');
|
|
|
|
|
}
|
|
|
|
|
function downloadImages(id) {
|
|
|
|
|
axios.get(`/api/image/batch/${id}/download`, {
|
|
|
|
|
responseType: 'blob'
|
|
|
|
|
}).then(res => {
|
|
|
|
|
const url = window.URL.createObjectURL(new Blob([res.data]));
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
|
a.href = url;
|
|
|
|
|
a.download = `batch_${id}_perturbed_images.zip`;
|
|
|
|
|
document.body.appendChild(a);
|
|
|
|
|
a.click();
|
|
|
|
|
a.remove();
|
|
|
|
|
window.URL.revokeObjectURL(url);
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 任务创建页
|
|
|
|
|
function loadCreateTaskPage() {
|
|
|
|
|
const el = document.getElementById('page-create');
|
|
|
|
|
el.innerHTML = '<h3>创建扰动任务</h3><div id="create-task-form"></div>';
|
|
|
|
|
// 获取算法列表
|
|
|
|
|
axios.get('/api/user/algorithms').then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
const perturbations = res.data.perturbation_algorithms;
|
|
|
|
|
// 获取用户配置
|
|
|
|
|
return axios.get('/api/task/1/config').catch(() => ({data: {current_config: {}}})).then(configRes => {
|
|
|
|
|
const config = configRes.data.suggested_config || configRes.data.current_config || {};
|
|
|
|
|
let form = `<form id='task-create-form'>
|
|
|
|
|
<div class='mb-2'>
|
|
|
|
|
<label>任务名称</label>
|
|
|
|
|
<input class='form-control' name='batch_name' placeholder='任务名(可选)'>
|
|
|
|
|
</div>
|
|
|
|
|
<div class='mb-2'>
|
|
|
|
|
<label>扰动算法</label>
|
|
|
|
|
<select class='form-select' name='perturbation_config_id'>
|
|
|
|
|
${perturbations.map(p=>`<option value='${p.id}' ${config.perturbation_config_id==p.id?'selected':''}>${p.method_name} - ${p.description}</option>`).join('')}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class='mb-2'>
|
|
|
|
|
<label>扰动强度 (1-255)</label>
|
|
|
|
|
<input class='form-control' type='number' name='epsilon' min='1' max='255' value='${config.epsilon||8}'>
|
|
|
|
|
</div>
|
|
|
|
|
<div class='mb-2'>
|
|
|
|
|
<label><input type='checkbox' name='use_strong_protection' ${config.use_strong_protection?'checked':''}> 使用强防护版本</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class='mb-2'>
|
|
|
|
|
<label>上传图片(支持单张或多张)</label>
|
|
|
|
|
<input class='form-control' type='file' name='files' multiple accept='image/*,.zip'>
|
|
|
|
|
</div>
|
|
|
|
|
<button class='btn btn-success' type='submit'>创建任务并上传图片</button>
|
|
|
|
|
</form>`;
|
|
|
|
|
document.getElementById('create-task-form').innerHTML = form;
|
|
|
|
|
document.getElementById('task-create-form').onsubmit = function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const formData = new FormData(this);
|
|
|
|
|
// 先创建任务
|
|
|
|
|
axios.post('/api/task/create', {
|
|
|
|
|
batch_name: formData.get('batch_name'),
|
|
|
|
|
perturbation_config_id: parseInt(formData.get('perturbation_config_id')),
|
|
|
|
|
epsilon: parseFloat(formData.get('epsilon')),
|
|
|
|
|
use_strong_protection: formData.get('use_strong_protection')==='on'
|
|
|
|
|
}).then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
const task = res.data.task;
|
|
|
|
|
alert(`任务创建成功!任务ID: ${task.id}, 微调任务ID: ${res.data.finetune_task_id}`);
|
|
|
|
|
// 上传图片
|
|
|
|
|
const uploadData = new FormData();
|
|
|
|
|
for (const file of formData.getAll('files')) uploadData.append('files', file);
|
|
|
|
|
return axios.post(`/api/task/upload/${task.id}`, uploadData, {headers:{'Content-Type':'multipart/form-data'}});
|
|
|
|
|
}).then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
alert(`成功上传 ${res.data.uploaded_files.length} 张图片`);
|
|
|
|
|
gotoPage('tasks');
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 个人信息页
|
|
|
|
|
function loadProfilePage() {
|
|
|
|
|
const el = document.getElementById('page-profile');
|
|
|
|
|
el.innerHTML = '<h3>个人信息</h3><div id="profile-content"></div>';
|
|
|
|
|
axios.get('/api/auth/profile').then(res => {
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
const user = res.data.user;
|
|
|
|
|
let html = `<div>用户名: ${user.username}</div><div>邮箱: ${user.email}</div>`;
|
|
|
|
|
html += `<button class='btn btn-primary mt-2' onclick='showChangePwd()'>修改密码</button>`;
|
|
|
|
|
html += `<button class='btn btn-secondary mt-2 ms-2' onclick='showChangeEmail()'>修改邮箱</button>`;
|
|
|
|
|
if (user.role === 'admin') html += `<button class='btn btn-danger mt-2 ms-2' onclick='gotoAdmin()'>管理员操作</button>`;
|
|
|
|
|
document.getElementById('profile-content').innerHTML = html;
|
|
|
|
|
}).catch(e => showRawData(e.response?.data || e));
|
|
|
|
|
}
|
|
|
|
|
function showChangePwd() {
|
|
|
|
|
const html = `<h5>修改密码</h5>
|
|
|
|
|
<form id='change-pwd-form'>
|
|
|
|
|
<div class='mb-2'><input class='form-control' name='old_password' type='password' placeholder='旧密码'></div>
|
|
|
|
|
<div class='mb-2'><input class='form-control' name='new_password' type='password' placeholder='新密码'></div>
|
|
|
|
|
<button class='btn btn-primary' type='submit'>提交</button>
|
|
|
|
|
<button class='btn btn-secondary ms-2' type='button' onclick='loadProfilePage()'>取消</button>
|
|
|
|
|
</form>`;
|
|
|
|
|
document.getElementById('profile-content').innerHTML = html;
|
|
|
|
|
document.getElementById('change-pwd-form').onsubmit = function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const data = Object.fromEntries(new FormData(this));
|
|
|
|
|
axios.post('/api/auth/change-password', data).then(res=>{
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
alert('密码修改成功');
|
|
|
|
|
loadProfilePage();
|
|
|
|
|
}).catch(e=>showRawData(e.response?.data||e));
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
function showChangeEmail() {
|
|
|
|
|
const html = `<h5>修改邮箱</h5>
|
|
|
|
|
<form id='change-email-form'>
|
|
|
|
|
<div class='mb-2'><input class='form-control' name='email' type='email' placeholder='新邮箱'></div>
|
|
|
|
|
<button class='btn btn-primary' type='submit'>提交</button>
|
|
|
|
|
<button class='btn btn-secondary ms-2' type='button' onclick='loadProfilePage()'>取消</button>
|
|
|
|
|
</form>`;
|
|
|
|
|
document.getElementById('profile-content').innerHTML = html;
|
|
|
|
|
document.getElementById('change-email-form').onsubmit = function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const data = Object.fromEntries(new FormData(this));
|
|
|
|
|
axios.put('/api/user/config', {email: data.email}).then(res=>{
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
alert('邮箱修改成功');
|
|
|
|
|
loadProfilePage();
|
|
|
|
|
}).catch(e=>showRawData(e.response?.data||e));
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
function gotoAdmin() {
|
|
|
|
|
// 管理员操作页面
|
|
|
|
|
axios.get('/api/admin/users').then(res=>{
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
const users = res.data.users||[];
|
|
|
|
|
let html = `<h5>用户管理</h5><table class='table'><thead><tr><th>ID</th><th>用户名</th><th>邮箱</th><th>角色</th><th>操作</th></tr></thead><tbody>`;
|
|
|
|
|
html += users.map(u=>`<tr><td>${u.id}</td><td>${u.username}</td><td>${u.email}</td><td>${u.role}</td><td><button class='btn btn-sm btn-danger' onclick='deleteUser(${u.id})'>删除</button></td></tr>`).join('');
|
|
|
|
|
html += '</tbody></table><button class="btn btn-secondary" onclick="loadProfilePage()">返回</button>';
|
|
|
|
|
document.getElementById('profile-content').innerHTML = html;
|
|
|
|
|
}).catch(e=>showRawData(e.response?.data||e));
|
|
|
|
|
}
|
|
|
|
|
function deleteUser(id) {
|
|
|
|
|
if(confirm('确定要删除该用户吗?')){
|
|
|
|
|
axios.delete(`/api/admin/users/${id}`).then(res=>{
|
|
|
|
|
showRawData(res.data);
|
|
|
|
|
alert('用户已删除');
|
|
|
|
|
gotoAdmin();
|
|
|
|
|
}).catch(e=>showRawData(e.response?.data||e));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 页面初始化
|
|
|
|
|
window.onload = function() {
|
|
|
|
|
getCurrentUser();
|
|
|
|
|
gotoPage('demo');
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|