v1.7.0 历史记录标注调用者/调用方式 + 每次提取自动按月归档原始HTML + 修复agent-browser HTML JSON转义bug

- 历史记录新增 caller(调用者: 游客/项目名) 与 call_method(调用方式: web/api)
  * 请求体 caller 或请求头 X-Caller/X-Project/X-App 标识调用者, 默认游客
  * 请求体 call_method 或 X-From 标识方式, 缺省按 Referer 自动判定(本前端=web, 否则api)
  * 前端提取请求带 X-From:web / X-Caller:游客; news-tracker 调用带 caller=news-tracker
- 每次提取后自动保存最原始 HTML 到 data/html/<YYYY-MM>/ 按月目录归档
  * screenshot/html/text/smart 四种动作均顺带抓取原始HTML并落盘
  * 历史详情新增 📄原始HTML文件 链接(GET /api/history/<id>/html), 删除记录连带删文件
- 修复: agent-browser 后端 html 提取返回 JSON 转义文本(非最原始HTML)的bug, 现解码为干净HTML
This commit is contained in:
2026-08-31 13:12:22 +08:00
parent e5ff90c46a
commit 9c63887e10
2 changed files with 163 additions and 34 deletions
+34 -4
View File
@@ -370,6 +370,16 @@
color: #c62828;
}
.badge-api {
background: #ede7f6;
color: #5e35b1;
}
.badge-web {
background: #e0f7fa;
color: #00695c;
}
.history-url {
max-width: 280px;
overflow: hidden;
@@ -556,7 +566,9 @@
.history-table th:nth-child(2),
.history-table td:nth-child(2),
.history-table th:nth-child(3),
.history-table td:nth-child(3) {
.history-table td:nth-child(3),
.history-table th:nth-child(4),
.history-table td:nth-child(4) {
display: none;
}
}
@@ -693,6 +705,8 @@
<tr>
<th>时间</th>
<th>类型</th>
<th>调用者</th>
<th>方式</th>
<th>网址</th>
<th>标题</th>
<th>状态</th>
@@ -717,7 +731,8 @@
<li>⏱️ <strong>页面加载等待</strong>:某些网站需要验证过程(如 Cloudflare),请设置较长时间(10000-30000ms</li>
<li>🔄 <strong>滚动次数</strong>:用于加载动态内容(如微博、推特等),建议 3-5 次</li>
<li>🎯 <strong>全页截图</strong>:滚动加载后建议开启此选项</li>
<li>📜 <strong>提取历史</strong>:每次提取自动入库,支持分页浏览、按类型筛选、搜索与删除</li>
<li>📜 <strong>提取历史</strong>:每次提取自动入库,支持分页浏览、按类型筛选、搜索与删除;每条记录会标注<strong>调用者</strong>(游客/项目名)与<strong>调用方式</strong>web/api),并自动把<strong>最原始 HTML</strong> 按月归档到 <code>data/html/&lt;月份&gt;/</code>(详情里可点「📄 原始HTML文件」查看)</li>
<li>👤 <strong>API 调用方标识</strong>:请求体带 <code>caller</code>(或请求头 <code>X-Caller</code>/<code>X-Project</code>)即可在历史里显示项目名;带 <code>call_method</code><code>X-From</code> 标注调用方式,缺省按 Referer 自动判定 web/api</li>
</ul>
<p style="margin-top: 15px;"><strong>API 调用:</strong></p>
<pre><code>POST /api/capture
@@ -729,6 +744,8 @@
"scroll_delay": 1000, // 可选:滚动间隔
"full_page": true, // 可选:全页截图
"backend": "playwright", // 可选:playwright / agent-browser
"caller": "news-tracker", // 可选:调用者标识(历史记录里显示,默认游客)
"call_method": "api", // 可选:调用方式 web/api(缺省自动判定)
"viewport": {"width":1280,"height":700},
"smart_config": { // 可选:临时覆盖按需截图 LLM 配置
"base_url": "https://www.autodl.art/api/v1",
@@ -957,7 +974,9 @@ DELETE /api/history/&lt;id&gt; // 删除历史</code></pre>
const response = await fetch('/api/capture', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'X-From': 'web',
'X-Caller': '游客'
},
body: JSON.stringify(currentData)
});
@@ -1111,9 +1130,14 @@ DELETE /api/history/&lt;id&gt; // 删除历史</code></pre>
const statusText = r.status === 'failed'
? `<span style="color:#c62828;" title="${escapeHtml(r.error || '')}">失败</span>`
: '<span style="color:#2e7d32;">成功</span>';
const callerText = r.caller || '游客';
const methodText = r.call_method || 'api';
const methodBadge = methodText === 'web' ? 'badge-web' : 'badge-api';
return `<tr>
<td style="white-space:nowrap;">${escapeHtml(r.created_at)}</td>
<td><span class="badge ${escapeHtml(badgeClass)}">${badgeText}</span></td>
<td><span class="history-title" title="${escapeHtml(callerText)}">${escapeHtml(callerText)}</span></td>
<td><span class="badge ${methodBadge}" style="font-size:11px;padding:2px 8px;">${escapeHtml(methodText)}</span></td>
<td><a class="history-url" href="${escapeHtml(r.url)}" target="_blank" rel="noopener">${escapeHtml(r.url)}</a></td>
<td><span class="history-title" title="${escapeHtml(r.title || '')}">${escapeHtml(r.title || '—')}</span></td>
<td>${statusText}</td>
@@ -1173,11 +1197,17 @@ DELETE /api/history/&lt;id&gt; // 删除历史</code></pre>
document.getElementById('modalTitle').textContent = (r.title || r.url || '记录详情');
const badgeClass = r.status === 'failed' ? 'badge-failed' : ('badge-' + r.action);
const badgeText = r.status === 'failed' ? '❌ 失败' : ({screenshot: '📸 截图', html: '📄 HTML', text: '📝 文本'}[r.action] || r.action);
const htmlLink = r.html_path
? `<span><a href="/api/history/${r.id}/html" target="_blank" style="color:#667eea;font-weight:600;" title="${escapeHtml(r.html_path)}">📄 原始HTML文件</a></span>`
: '';
document.getElementById('modalMeta').innerHTML =
`<span>类型:<span class="badge ${badgeClass}">${badgeText}</span></span>` +
`<span>时间:${escapeHtml(r.created_at)}</span>` +
`<span>调用者:${escapeHtml(r.caller || '游客')}</span>` +
`<span>方式:${escapeHtml(r.call_method || 'api')}</span>` +
`<span>后端:${escapeHtml(r.backend || '—')}</span>` +
`<span>状态:${r.status === 'failed' ? '<span style="color:#c62828;">失败</span>' : '<span style="color:#2e7d32;">成功</span>'}</span>`;
`<span>状态:${r.status === 'failed' ? '<span style="color:#c62828;">失败</span>' : '<span style="color:#2e7d32;">成功</span>'}</span>` +
htmlLink;
const body = document.getElementById('modalBody');
if (r.status === 'failed') {