将lianghao_branch合并到develop #21

Merged
hnu202326010204 merged 8 commits from lianghao_branch into develop 1 month ago

@ -1437,12 +1437,12 @@ Authorization: Bearer <token>
**不同任务类型返回的图片**
| 任务类型 | 返回图片类型 |
| ------------ | ------------------------------------------------------------ |
| perturbation | original, perturbed |
| 任务类型 | 返回图片类型 |
| ------------ | ------------------------------------------------------------------ |
| perturbation | original, perturbed |
| finetune | original, original_generate, perturbed_generate, uploaded_generate |
| heatmap | heatmap |
| evaluate | original_generate, perturbed_generate, report |
| heatmap | heatmap |
| evaluate | original_generate, perturbed_generate, report |
失败 (404):
@ -1547,3 +1547,21 @@ Authorization: Bearer <token>
| 403 | 无权限访问 |
| 404 | 资源不存在 |
| 500 | 服务器内部错误 |
---
## 五、任务日志相关
### GET `/api/task/<task_id>/logs`
**功能**:获取指定任务的运行日志。
**认证**:是
**成功响应** `200 OK`
```json
{
"logs": "2025-12-12 10:00:00 - INFO - Starting task...\n2025-12-12 10:00:01 - INFO - Processing..."
}
```
**错误响应**
- `401 {"error": "无效的用户身份标识"}`
- `404 {"error": "任务不存在或无权限"}`
- `500 {"error": "读取日志失败: ..."}`

@ -15,6 +15,10 @@ from app.database import (
from app.services.task_service import TaskService
from app.services.image_service import ImageService
import os
import glob
from config.algorithm_config import AlgorithmConfig
task_bp = Blueprint('task', __name__)
@ -65,6 +69,33 @@ def get_task_status(task_id, current_user_id):
return jsonify(status), 200
@task_bp.route('/<int:task_id>/logs', methods=['GET'])
@int_jwt_required
def get_task_logs(task_id, current_user_id):
"""获取任务日志"""
task = Task.query.get(task_id)
if not TaskService.ensure_task_owner(task, current_user_id):
return TaskService.json_error('任务不存在或无权限', 404)
log_dir = AlgorithmConfig.LOGS_DIR
# 匹配模式:*task_{task_id}_*.log
pattern = os.path.join(log_dir, f'*task_{task_id}_*.log')
log_files = glob.glob(pattern)
if not log_files:
return jsonify({'logs': '暂无日志'}), 200
# 如果有多个,取修改时间最新的一个
latest_log = max(log_files, key=os.path.getmtime)
try:
with open(latest_log, 'r', encoding='utf-8') as f:
content = f.read()
return jsonify({'logs': content}), 200
except Exception as e:
return TaskService.json_error(f'读取日志失败: {str(e)}', 500)
@task_bp.route('/<int:task_id>/cancel', methods=['POST'])
@int_jwt_required
def cancel_task(task_id, current_user_id):

@ -60,7 +60,7 @@ class User(db.Model):
'user_id': self.user_id,
'username': self.username,
'email': self.email,
'role': self.role.name,
'role': self.role.role_code,
'is_active': self.is_active,
'created_at': self.created_at.isoformat(),
'updated_at': self.updated_at.isoformat()

@ -7,6 +7,7 @@ import os
import subprocess
import logging
import shutil
import glob
from datetime import datetime
logging.basicConfig(
@ -69,10 +70,28 @@ def run_evaluate_task(task_id, clean_ref_dir, clean_output_dir,
elif os.path.isdir(item_path):
shutil.rmtree(item_path)
# 设置日志文件
log_dir = AlgorithmConfig.LOGS_DIR
os.makedirs(log_dir, exist_ok=True)
# 清除旧日志
old_logs = glob.glob(os.path.join(log_dir, f'evaluate_task_{task_id}_*.log'))
for old_log in old_logs:
try:
os.remove(old_log)
logger.info(f"Removed old log file: {old_log}")
except Exception as e:
logger.warning(f"Failed to remove old log file {old_log}: {e}")
log_file = os.path.join(
log_dir,
f'evaluate_task_{task_id}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
)
# 运行真实评估算法
result = _run_real_evaluate(
task_id, clean_ref_dir, clean_output_dir,
perturbed_output_dir, output_dir, image_size
perturbed_output_dir, output_dir, image_size, log_file
)
# 保存评估结果文件路径到数据库
@ -105,11 +124,11 @@ def run_evaluate_task(task_id, clean_ref_dir, clean_output_dir,
def _run_real_evaluate(task_id, clean_ref_dir, clean_output_dir,
perturbed_output_dir, output_dir, image_size):
"""运行真实数值评估算法"""
perturbed_output_dir, output_dir, image_size, log_file):
"""运行真实评估算法"""
from config.algorithm_config import AlgorithmConfig
logger.info(f"Running real evaluate generation")
logger.info(f"Running real evaluation")
# 获取评估脚本配置
evaluate_config = AlgorithmConfig.EVALUATE_SCRIPTS.get('numbers', {})
@ -143,14 +162,6 @@ def _run_real_evaluate(task_id, clean_ref_dir, clean_output_dir,
env = os.environ.copy()
env['HF_HUB_OFFLINE'] = '1'
# 设置日志文件
log_dir = AlgorithmConfig.LOGS_DIR
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(
log_dir,
f'evaluate_{task_id}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
)
# 执行命令
with open(log_file, 'w') as f:
process = subprocess.Popen(

@ -99,11 +99,43 @@ def run_finetune_task(task_id, finetune_method, train_images_dir,
coords_dir = os.path.dirname(coords_save_path)
os.makedirs(coords_dir, exist_ok=True)
# 设置日志文件
log_dir = AlgorithmConfig.LOGS_DIR
os.makedirs(log_dir, exist_ok=True)
if not is_perturbed:
# 原图微调:清除旧日志,创建新日志
old_logs = glob.glob(os.path.join(log_dir, f'finetune_{finetune_method}_task_{task_id}_*.log'))
for old_log in old_logs:
try:
os.remove(old_log)
logger.info(f"Removed old log file: {old_log}")
except Exception as e:
logger.warning(f"Failed to remove old log file {old_log}: {e}")
log_file = os.path.join(
log_dir,
f'finetune_{finetune_method}_task_{task_id}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
)
else:
# 扰动图微调:尝试复用现有日志
old_logs = glob.glob(os.path.join(log_dir, f'finetune_{finetune_method}_task_{task_id}_*.log'))
if old_logs:
# 找到最新的日志文件
log_file = max(old_logs, key=os.path.getmtime)
logger.info(f"Appending to existing log file: {log_file}")
else:
# 没找到则创建新的
log_file = os.path.join(
log_dir,
f'finetune_{finetune_method}_task_{task_id}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
)
# 运行真实微调算法
result = _run_real_finetune(
finetune_method, task_id, train_images_dir, output_model_dir,
class_dir, coords_save_path, validation_output_dir,
instance_prompt, class_prompt, validation_prompt, is_perturbed, custom_params
instance_prompt, class_prompt, validation_prompt, is_perturbed, custom_params, log_file
)
# 保存生成的验证图片到数据库
@ -138,7 +170,7 @@ def run_finetune_task(task_id, finetune_method, train_images_dir,
def _run_real_finetune(finetune_method, task_id, train_images_dir, output_model_dir,
class_dir, coords_save_path, validation_output_dir,
instance_prompt, class_prompt, validation_prompt, is_perturbed, custom_params):
instance_prompt, class_prompt, validation_prompt, is_perturbed, custom_params, log_file):
"""
运行真实微调算法参考sh脚本配置
@ -155,6 +187,7 @@ def _run_real_finetune(finetune_method, task_id, train_images_dir, output_model_
validation_prompt: 验证提示词
is_perturbed: 是否使用扰动图片
custom_params: 自定义参数
log_file: 日志文件路径
"""
from config.algorithm_config import AlgorithmConfig
@ -233,17 +266,12 @@ def _run_real_finetune(finetune_method, task_id, train_images_dir, output_model_
logger.info(f"Executing command: {' '.join(cmd)}")
# 设置日志文件
log_dir = AlgorithmConfig.LOGS_DIR
os.makedirs(log_dir, exist_ok=True)
image_type = 'perturbed' if is_perturbed else 'original'
log_file = os.path.join(
log_dir,
f'finetune_{image_type}_{task_id}_{finetune_method}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
)
# 执行命令
with open(log_file, 'w') as f:
# 使用追加模式 'a',以便在同一日志文件中记录原图和扰动图的微调过程
with open(log_file, 'a') as f:
if is_perturbed:
f.write(f"\n\n{'='*30}\nStarting Perturbed Finetune Task\n{'='*30}\n\n")
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,

@ -8,6 +8,7 @@ import os
import subprocess
import logging
import shutil
import glob
from datetime import datetime
logging.basicConfig(
@ -93,10 +94,28 @@ def run_heatmap_task(task_id, original_image_path, perturbed_image_path,
elif os.path.isdir(item_path):
shutil.rmtree(item_path)
# 设置日志文件
log_dir = AlgorithmConfig.LOGS_DIR
os.makedirs(log_dir, exist_ok=True)
# 清除旧日志
old_logs = glob.glob(os.path.join(log_dir, f'heatmap_task_{task_id}_*.log'))
for old_log in old_logs:
try:
os.remove(old_log)
logger.info(f"Removed old log file: {old_log}")
except Exception as e:
logger.warning(f"Failed to remove old log file {old_log}: {e}")
log_file = os.path.join(
log_dir,
f'heatmap_task_{task_id}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
)
# 运行真实热力图算法
result = _run_real_heatmap(
task_id, original_image_path, perturbed_image_path,
prompt_text, target_word, output_dir
prompt_text, target_word, output_dir, log_file
)
# 保存热力图文件到数据库
@ -131,7 +150,7 @@ def run_heatmap_task(task_id, original_image_path, perturbed_image_path,
def _run_real_heatmap(task_id, original_image_path, perturbed_image_path,
prompt_text, target_word, output_dir):
prompt_text, target_word, output_dir, log_file):
"""运行真实热力图算法"""
from config.algorithm_config import AlgorithmConfig
@ -175,14 +194,6 @@ def _run_real_heatmap(task_id, original_image_path, perturbed_image_path,
env = os.environ.copy()
env['HF_HUB_OFFLINE'] = '1'
# 设置日志文件
log_dir = AlgorithmConfig.LOGS_DIR
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(
log_dir,
f'heatmap_{task_id}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
)
# 执行命令
with open(log_file, 'w') as f:
process = subprocess.Popen(

@ -85,10 +85,28 @@ def run_perturbation_task(task_id, algorithm_code, epsilon, input_dir, output_di
elif os.path.isdir(item_path):
shutil.rmtree(item_path)
# 设置日志文件
log_dir = AlgorithmConfig.LOGS_DIR
os.makedirs(log_dir, exist_ok=True)
# 清除旧日志
old_logs = glob.glob(os.path.join(log_dir, f'perturbation_{algorithm_code}_task_{task_id}_*.log'))
for old_log in old_logs:
try:
os.remove(old_log)
logger.info(f"Removed old log file: {old_log}")
except Exception as e:
logger.warning(f"Failed to remove old log file {old_log}: {e}")
log_file = os.path.join(
log_dir,
f'perturbation_{algorithm_code}_task_{task_id}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
)
# 运行真实算法
result = _run_real_algorithm(
script_path, conda_env, algorithm_code, task_id,
epsilon, input_dir, output_dir, class_dir, custom_params
epsilon, input_dir, output_dir, class_dir, custom_params, log_file
)
# 保存扰动图片到数据库
@ -119,7 +137,7 @@ def run_perturbation_task(task_id, algorithm_code, epsilon, input_dir, output_di
def _run_real_algorithm(script_path, conda_env, algorithm_code, task_id,
epsilon, input_dir, output_dir, class_dir, custom_params):
epsilon, input_dir, output_dir, class_dir, custom_params, log_file):
"""
运行真实算法参考sh脚本配置
@ -196,6 +214,15 @@ def _run_real_algorithm(script_path, conda_env, algorithm_code, task_id,
f"--output_dir={output_dir}",
f"--eps={int(epsilon)}",
])
elif algorithm_code == 'glaze':
# Glaze参数结构
cmd_args.extend([
f"--instance_data_dir={input_dir}",
f"--output_dir={output_dir}",
])
# 使用任务配置的epsilon覆盖默认值
if epsilon is not None:
params['eps'] = float(epsilon)
else:
raise ValueError(f"Unsupported algorithm code: {algorithm_code}")
@ -219,14 +246,6 @@ def _run_real_algorithm(script_path, conda_env, algorithm_code, task_id,
logger.info(f"Executing command: {' '.join(cmd)}")
# 设置日志文件
log_dir = AlgorithmConfig.LOGS_DIR
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(
log_dir,
f'task_{task_id}_{algorithm_code}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
)
# 执行命令
with open(log_file, 'w') as f:
process = subprocess.Popen(

@ -38,6 +38,7 @@ class AlgorithmConfig:
'simac': os.getenv('CONDA_ENV_SIMAC', 'simac'),
'caat': os.getenv('CONDA_ENV_CAAT', 'caat'),
'pid': os.getenv('CONDA_ENV_PID', 'pid'),
'glaze': os.getenv('CONDA_ENV_GLAZE', 'pid'),
'dreambooth': os.getenv('CONDA_ENV_DREAMBOOTH', 'pid'),
'lora': os.getenv('CONDA_ENV_LORA', 'pid'),
'textual_inversion': os.getenv('CONDA_ENV_TI', 'pid'),
@ -126,6 +127,24 @@ class AlgorithmConfig:
'center_crop': True,
'attack_type': 'add-log'
}
},
'glaze': {
'real_script': os.path.join(ALGORITHMS_DIR, 'perturbation', 'glaze.py'),
'virtual_script': os.path.join(ALGORITHMS_DIR, 'perturbation_engine.py'),
'conda_env': CONDA_ENVS['glaze'],
'default_params': {
'pretrained_model_name_or_path': MODELS_DIR['model2'],
'resolution': 512,
'center_crop': True,
'max_train_steps': 150,
'eps': 0.05,
'target_style': 'cubism painting by picasso',
'style_strength': 0.75,
'n_runs': 3,
'style_transfer_iter': 15,
'guidance_scale': 7.5,
'seed': 42
}
}
}

@ -15,7 +15,7 @@ def init_database():
# 初始化角色数据
roles = [
{'role_id': 1, 'role_code': 'admin', 'name': '管理员', 'max_concurrent_tasks': 15, 'description': '系统管理员,拥有最高权限'},
{'role_id': 1, 'role_code': 'admin', 'name': '管理员', 'max_concurrent_tasks': 1000, 'description': '系统管理员,拥有最高权限'},
{'role_id': 2, 'role_code': 'vip', 'name': 'VIP用户', 'max_concurrent_tasks': 10, 'description': '付费用户,享有较高的资源使用权限'},
{'role_id': 3, 'role_code': 'normal', 'name': '普通用户', 'max_concurrent_tasks': 5, 'description': '免费用户,享有基本的资源使用权限'}
]
@ -60,7 +60,8 @@ def init_database():
{'perturbation_code': 'aspl', 'perturbation_name': 'ASPL算法', 'description': 'Advanced Semantic Protection Layer for Enhanced Privacy Defense'},
{'perturbation_code': 'simac', 'perturbation_name': 'SimAC算法', 'description': 'Simple Anti-Customization Method for Protecting Face Privacy'},
{'perturbation_code': 'caat', 'perturbation_name': 'CAAT算法', 'description': 'Perturbing Attention Gives You More Bang for the Buck'},
{'perturbation_code': 'pid', 'perturbation_name': 'PID算法', 'description': 'Prompt-Independent Data Protection Against Latent Diffusion Models'}
{'perturbation_code': 'pid', 'perturbation_name': 'PID算法', 'description': 'Prompt-Independent Data Protection Against Latent Diffusion Models'},
{'perturbation_code': 'glaze', 'perturbation_name': 'Glaze算法', 'description': 'Protecting Artists from Style Mimicry by Text-to-Image Models'}
]
for config in perturbation_configs:

@ -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>

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save