## 新增功能 - 文件管理模块:分类树 + 文件上传/下载/删除 - 文件上传支持拖拽(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>
198 lines
11 KiB
HTML
198 lines
11 KiB
HTML
<!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>
|