AgentEvalTool/mockup/layout-v2.html
sinohqb a77cd83e6a v0.2.0-dev: 文件管理 + 页面布局统一 + 6 个 bug 修复
## 新增功能
- 文件管理模块:分类树 + 文件上传/下载/删除
- 文件上传支持拖拽(Dragger)+ 手动上传(customRequest 模式)

## 页面布局统一(参照评测执行页)
- 仪表盘/评测对象/评测场景/评测报告 全部改为全高 flex 布局
- 统一内联页头样式(h2 + 竖线分隔 + 描述)
- 表格撑满高度、overflow 处理
- 每页添加刷新按钮

## Bug 修复
- 分类树操作按钮 hover 不可见(CSS 规则缺失)
- 文件上传失败(multipart boundary 缺失)
- LLM API 响应 content blocks 数组格式支持(_extract_content_from_api_response)
- response_time_max_ms 被静默忽略(隐式规则传空 params)
- 空 messages 导致 IndexError 崩溃
- poll_reply 异常中止整个 run(缺 try/catch)
- engine finally 未关闭 session
- 3 个页面 UTC 时间戳解析偏差 8 小时

## 后端
- EvalEngine: poll_reply 异常保护、空 dialog 保护、session 关闭
- LLM API 响应解析支持 content-block-array 格式
- 隐式 response_time 规则正确传递 max_ms 参数

## 前端
- api.ts: 移除手动 Content-Type(让浏览器自动添加 boundary)
- Files.tsx: customRequest 替代 beforeUpload、布局优化
- index.css: 分类树 hover 规则
- Targets/Scenarios/Home/Reports: 全高布局改造
- 3 个页面时间戳改用 formatDateTime()(修复 UTC 偏差)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-16 15:25:22 +08:00

198 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AgentEvalTool - Layout Mockup</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; height: 100vh; overflow: hidden; }
/* Sidebar */
.sidebar { width: 208px; background: #001529; display: flex; flex-direction: column; flex-shrink: 0; }
.sidebar-logo { height: 48px; display: flex; align-items: center; padding: 0 16px; color: #fff; font-size: 16px; font-weight: 600; border-bottom: 1px solid rgba(255,255,255,0.08); }
.sidebar-menu { flex: 1; padding: 8px 0; }
.menu-item { display: flex; align-items: center; padding: 10px 16px; color: rgba(255,255,255,0.75); cursor: pointer; font-size: 14px; transition: all 0.2s; gap: 10px; }
.menu-item:hover { color: #fff; background: rgba(255,255,255,0.04); }
.menu-item.active { color: #fff; background: #1677ff; border-radius: 0; margin: 0 8px; border-radius: 6px; }
.menu-icon { width: 16px; height: 16px; opacity: 0.85; }
/* Main area */
.main { flex: 1; display: flex; flex-direction: column; background: #1a1a2e; overflow: hidden; }
/* Tab bar */
.tab-bar { display: flex; align-items: flex-end; background: #001529; padding: 0 4px; gap: 2px; flex-shrink: 0; height: 40px; }
.tab { display: flex; align-items: center; gap: 6px; padding: 8px 16px; font-size: 13px; cursor: pointer; border-radius: 8px 8px 0 0; transition: all 0.15s; color: rgba(255,255,255,0.65); background: transparent; position: relative; }
.tab:hover { color: rgba(255,255,255,0.9); background: rgba(255,255,255,0.06); }
.tab.active { color: #fff; background: #1a1a2e; }
.tab-close { width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; border-radius: 4px; font-size: 11px; opacity: 0; transition: all 0.15s; }
.tab:hover .tab-close { opacity: 0.6; }
.tab-close:hover { opacity: 1 !important; background: rgba(255,255,255,0.15); }
.tab-icon { font-size: 13px; }
/* Content area */
.content { flex: 1; overflow: auto; }
/* Scenario A: Dashboard (light page on dark bg) */
.dashboard-mock { padding: 20px; }
.dash-card { background: #fff; border-radius: 8px; padding: 20px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.3); }
.dash-card h3 { font-size: 15px; color: #1a1a2e; margin-bottom: 12px; }
.dash-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
.stat-box { background: #f8f9fc; border-radius: 8px; padding: 16px; text-align: center; }
.stat-box .num { font-size: 28px; font-weight: 700; color: #1677ff; }
.stat-box .label { font-size: 12px; color: #888; margin-top: 4px; }
/* Scenario B: OpenClaw (dark iframe) */
.openclaw-mock { width: 100%; height: 100%; background: #0f1117; display: flex; flex-direction: column; }
.oc-header { padding: 16px 20px; border-bottom: 1px solid #2a2a3a; display: flex; align-items: center; gap: 12px; }
.oc-avatar { width: 32px; height: 32px; border-radius: 50%; background: linear-gradient(135deg, #667eea, #764ba2); display: flex; align-items: center; justify-content: center; color: #fff; font-size: 14px; font-weight: 600; }
.oc-title { color: #e0e0e0; font-size: 15px; font-weight: 500; }
.oc-chat { flex: 1; padding: 20px; display: flex; flex-direction: column; gap: 16px; }
.oc-msg { max-width: 70%; padding: 12px 16px; border-radius: 12px; font-size: 14px; line-height: 1.6; }
.oc-msg.user { background: #1677ff; color: #fff; align-self: flex-end; border-bottom-right-radius: 4px; }
.oc-msg.assistant { background: #1e1e2e; color: #d0d0d0; align-self: flex-start; border-bottom-left-radius: 4px; border: 1px solid #2a2a3a; }
.oc-input { padding: 16px 20px; border-top: 1px solid #2a2a3a; display: flex; gap: 12px; }
.oc-input input { flex: 1; background: #1e1e2e; border: 1px solid #2a2a3a; border-radius: 8px; padding: 10px 16px; color: #e0e0e0; font-size: 14px; outline: none; }
.oc-input button { background: #1677ff; color: #fff; border: none; border-radius: 8px; padding: 10px 20px; cursor: pointer; font-size: 14px; }
/* Toggle */
.toggle-bar { position: fixed; top: 12px; right: 12px; z-index: 999; display: flex; gap: 8px; }
.toggle-btn { padding: 6px 14px; border-radius: 6px; border: 1px solid #444; background: #222; color: #ccc; font-size: 12px; cursor: pointer; }
.toggle-btn.active { background: #1677ff; color: #fff; border-color: #1677ff; }
.hidden { display: none !important; }
</style>
</head>
<body>
<!-- Toggle buttons to switch views -->
<div class="toggle-bar">
<button class="toggle-btn active" onclick="showView('dashboard')">仪表盘视图</button>
<button class="toggle-btn" onclick="showView('openclaw')">AI 助手视图</button>
</div>
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-logo">AgentEvalTool</div>
<div class="sidebar-menu">
<div class="menu-item active" id="menu-dashboard" onclick="handleMenu('dashboard')">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
仪表盘
</div>
<div class="menu-item" id="menu-targets" onclick="handleMenu('targets')">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>
评测对象
</div>
<div class="menu-item" id="menu-scenarios" onclick="handleMenu('scenarios')">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
评测场景
</div>
<div class="menu-item" id="menu-runs" onclick="handleMenu('runs')">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/></svg>
评测执行
</div>
<div class="menu-item" id="menu-reports" onclick="handleMenu('reports')">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>
评测报告
</div>
<div class="menu-item" id="menu-openclaw" onclick="handleMenu('openclaw')">
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="3"/><line x1="8" y1="16" x2="8" y2="16"/><line x1="16" y1="16" x2="16" y2="16"/></svg>
AI 助手
</div>
</div>
</div>
<!-- Main area -->
<div class="main">
<!-- Tab bar at the very top -->
<div class="tab-bar">
<div class="tab active" id="tab-dashboard" onclick="switchTab('dashboard')">
<svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
仪表盘
</div>
<div class="tab" id="tab-openclaw" onclick="switchTab('openclaw')">
<svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="3"/></svg>
AI 助手
<span class="tab-close" onclick="event.stopPropagation();closeTab('openclaw')">x</span>
</div>
</div>
<!-- Content: Dashboard -->
<div class="content" id="view-dashboard">
<div class="dashboard-mock">
<div class="dash-card">
<h3>概览统计</h3>
<div class="dash-stats">
<div class="stat-box"><div class="num">12</div><div class="label">评测对象</div></div>
<div class="stat-box"><div class="num">8</div><div class="label">评测场景</div></div>
<div class="stat-box"><div class="num">156</div><div class="label">执行记录</div></div>
<div class="stat-box"><div class="num">23</div><div class="label">评测报告</div></div>
</div>
</div>
<div class="dash-card">
<h3>最近执行</h3>
<div style="color: #666; font-size: 13px; line-height: 2;">
<div style="display:flex; justify-content:space-between; border-bottom: 1px solid #f0f0f0; padding: 8px 0;">
<span>场景A - 目标X 评测</span><span style="color: #52c41a;">通过</span><span style="color:#999">2026-07-10 14:30</span>
</div>
<div style="display:flex; justify-content:space-between; border-bottom: 1px solid #f0f0f0; padding: 8px 0;">
<span>场景B - 目标Y 评测</span><span style="color: #faad14;">部分通过</span><span style="color:#999">2026-07-10 12:15</span>
</div>
<div style="display:flex; justify-content:space-between; padding: 8px 0;">
<span>场景C - 目标Z 评测</span><span style="color: #ff4d4f;">失败</span><span style="color:#999">2026-07-09 18:42</span>
</div>
</div>
</div>
</div>
</div>
<!-- Content: OpenClaw -->
<div class="content hidden" id="view-openclaw">
<div class="openclaw-mock">
<div class="oc-header">
<div class="oc-avatar">A</div>
<div class="oc-title">Assistant</div>
</div>
<div class="oc-chat">
<div class="oc-msg assistant">你好!我是 AI 助手,可以帮你编辑技能、配置策略并执行评测任务。有什么需要帮助的吗?</div>
<div class="oc-msg user">帮我创建一个评测技能,用于检测 API 响应时间</div>
<div class="oc-msg assistant">好的,我来帮你创建 API 响应时间检测技能。请告诉我:<br><br>1. 目标 API 的地址是什么?<br>2. 期望的响应时间阈值是多少毫秒?<br>3. 需要检测哪些 HTTP 方法GET/POST/PUT</div>
</div>
<div class="oc-input">
<input type="text" placeholder="输入消息..." />
<button>发送</button>
</div>
</div>
</div>
</div>
<script>
function showView(view) {
document.querySelectorAll('.toggle-btn').forEach(b => b.classList.remove('active'));
event.target.classList.add('active');
switchTab(view);
}
function switchTab(tab) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.content').forEach(c => c.classList.add('hidden'));
document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'));
document.getElementById('tab-' + tab).classList.add('active');
document.getElementById('view-' + tab).classList.remove('hidden');
document.getElementById('menu-' + tab).classList.add('active');
}
function handleMenu(menu) {
switchTab(menu);
document.querySelectorAll('.toggle-btn').forEach(b => b.classList.remove('active'));
}
function closeTab(tab) {
document.getElementById('tab-' + tab).classList.add('hidden');
switchTab('dashboard');
}
</script>
</body>
</html>