feat: add config-owned direct submit runtime

Keep browser-attached workflows on the configured direct-skill path and align the Zhihu export/browser regression contracts with the current ws merge state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
木炎
2026-04-11 15:45:42 +08:00
29 changed files with 5218 additions and 585 deletions

View File

@@ -0,0 +1,637 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>知乎热榜图表驾驶舱</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<style>
:root {
--bg: #06111f;
--bg-2: #0a1f37;
--panel: rgba(8, 25, 42, 0.88);
--panel-strong: rgba(10, 32, 55, 0.95);
--line: rgba(101, 187, 255, 0.18);
--line-strong: rgba(236, 186, 81, 0.26);
--text: #eef6ff;
--muted: #8ea6c2;
--accent: #62d0ff;
--accent-2: #ecba51;
--accent-3: #6df0c2;
--danger: #ff8b7e;
--shadow: 0 20px 48px rgba(0, 0, 0, 0.34);
--font-heading: "DIN Alternate", "Bahnschrift", "Microsoft YaHei UI", sans-serif;
--font-body: "Segoe UI Variable Text", "Microsoft YaHei", "PingFang SC", sans-serif;
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
min-height: 100%;
background:
radial-gradient(circle at 16% 10%, rgba(98, 208, 255, 0.18), transparent 22%),
radial-gradient(circle at 86% 12%, rgba(236, 186, 81, 0.14), transparent 18%),
linear-gradient(145deg, var(--bg) 0%, var(--bg-2) 42%, #030910 100%);
color: var(--text);
font-family: var(--font-body);
}
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(101, 187, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(101, 187, 255, 0.05) 1px, transparent 1px);
background-size: 44px 44px;
mask-image: radial-gradient(circle at center, black 34%, rgba(0, 0, 0, 0.22) 88%, transparent 100%);
}
.page {
min-height: 100vh;
padding: 18px;
display: grid;
grid-template-rows: auto auto 1fr auto;
gap: 14px;
}
.panel {
position: relative;
overflow: hidden;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.01)),
linear-gradient(145deg, rgba(9, 30, 51, 0.97), rgba(6, 20, 34, 0.92));
border: 1px solid var(--line);
border-radius: 22px;
box-shadow: var(--shadow);
}
.panel::before {
content: "";
position: absolute;
left: 18px;
right: 18px;
top: 0;
height: 2px;
background: linear-gradient(90deg, transparent, var(--accent), var(--accent-2), transparent);
opacity: 0.95;
}
.hero {
padding: 18px 24px;
display: grid;
grid-template-columns: minmax(0, 1fr) 360px;
gap: 16px;
align-items: center;
}
.eyebrow {
color: var(--accent);
letter-spacing: 2px;
text-transform: uppercase;
font-size: 12px;
margin-bottom: 8px;
}
h1 {
margin: 0;
font-family: var(--font-heading);
font-size: 38px;
line-height: 1.08;
letter-spacing: 1px;
}
#snapshot-meta {
margin: 10px 0 0;
color: var(--muted);
font-size: 14px;
}
.hero-notes {
display: grid;
gap: 10px;
}
.note-card {
padding: 14px 16px;
border-radius: 16px;
background: linear-gradient(135deg, rgba(98, 208, 255, 0.08), rgba(236, 186, 81, 0.08));
border: 1px solid rgba(255, 255, 255, 0.05);
}
.note-card strong {
display: block;
margin-bottom: 6px;
font-size: 14px;
}
.note-card span {
color: var(--muted);
font-size: 12px;
line-height: 1.5;
}
.metrics {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.metric {
padding: 18px 18px 16px;
}
.metric-label {
color: var(--muted);
font-size: 12px;
letter-spacing: 1px;
text-transform: uppercase;
}
.metric-value {
margin-top: 10px;
font-family: var(--font-heading);
font-size: 34px;
color: var(--text);
}
.metric-sub {
margin-top: 8px;
color: var(--accent);
font-size: 12px;
}
.charts {
min-height: 0;
display: grid;
grid-template-columns: 1.2fr 1fr 0.95fr;
grid-template-rows: 360px 320px;
gap: 14px;
grid-template-areas:
"bar top pie"
"bubble table table";
}
.chart-panel {
padding: 14px 16px 12px;
}
.bar-panel { grid-area: bar; }
.top-panel { grid-area: top; }
.pie-panel { grid-area: pie; }
.bubble-panel { grid-area: bubble; }
.table-panel { grid-area: table; padding: 14px 16px 10px; }
.section-head {
display: flex;
align-items: end;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.section-head h2 {
margin: 0;
font-size: 22px;
font-family: var(--font-heading);
letter-spacing: 1px;
}
.section-head span {
color: var(--muted);
font-size: 12px;
}
.chart {
width: 100%;
height: calc(100% - 42px);
}
.table-wrap {
height: calc(100% - 42px);
overflow: auto;
padding-right: 4px;
}
table {
width: 100%;
border-collapse: collapse;
}
thead th {
position: sticky;
top: 0;
z-index: 1;
background: rgba(6, 19, 32, 0.96);
padding: 10px 8px;
text-align: left;
font-size: 12px;
color: var(--muted);
letter-spacing: 1px;
text-transform: uppercase;
border-bottom: 1px solid var(--line-strong);
}
tbody td {
padding: 11px 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
font-size: 13px;
vertical-align: top;
}
tbody tr:nth-child(odd) {
background: rgba(255, 255, 255, 0.016);
}
.rank {
font-family: var(--font-heading);
color: var(--accent-2);
white-space: nowrap;
}
.heat {
color: var(--accent-3);
font-family: var(--font-heading);
white-space: nowrap;
}
.tag {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 999px;
background: rgba(98, 208, 255, 0.12);
color: var(--accent);
font-size: 12px;
}
.footer {
padding: 10px 16px;
color: var(--muted);
font-size: 12px;
}
@media (max-width: 1440px) {
.hero {
grid-template-columns: 1fr;
}
.metrics {
grid-template-columns: repeat(2, 1fr);
}
.charts {
grid-template-columns: 1fr;
grid-template-rows: 320px 320px 320px 320px 420px;
grid-template-areas:
"bar"
"top"
"pie"
"bubble"
"table";
}
}
@media (max-width: 760px) {
.page {
padding: 12px;
}
h1 {
font-size: 28px;
}
.metrics {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="page">
<section class="panel hero">
<div>
<div class="eyebrow">Zhihu Hotlist Visual Command Center</div>
<h1>知乎热榜图表驾驶舱</h1>
<p id="snapshot-meta">由 sgClaw screen_html_export 生成的本地静态展示页</p>
</div>
<div class="hero-notes">
<div class="note-card">
<strong>图表表达</strong>
<span>同一份热榜数据同时映射为分类热度、头部热点、结构占比和热度散点,适合现场讲解图表能力。</span>
</div>
<div class="note-card">
<strong>演示建议</strong>
<span id="lead-summary">优先讲解榜首热点、分类分布与热度层级,再向下展开全量榜单细节。</span>
</div>
</div>
</section>
<section class="metrics">
<article class="panel metric">
<div class="metric-label">热榜条目数</div>
<div id="metric-total" class="metric-value">0</div>
<div class="metric-sub">Tracked items</div>
</article>
<article class="panel metric">
<div class="metric-label">主题分类数</div>
<div id="metric-categories" class="metric-value">0</div>
<div class="metric-sub">Topic groups</div>
</article>
<article class="panel metric">
<div class="metric-label">累计热度</div>
<div id="metric-heat" class="metric-value">0</div>
<div class="metric-sub">Total heat</div>
</article>
<article class="panel metric">
<div class="metric-label">头部峰值</div>
<div id="metric-peak" class="metric-value">0</div>
<div class="metric-sub">Peak topic heat</div>
</article>
</section>
<section class="charts">
<section class="panel chart-panel bar-panel">
<div class="section-head">
<h2>分类总热度</h2>
<span>横向对比</span>
</div>
<div id="bar-chart" class="chart"></div>
</section>
<section class="panel chart-panel top-panel">
<div class="section-head">
<h2>Top10 热点</h2>
<span>柱状排行</span>
</div>
<div id="top-chart" class="chart"></div>
</section>
<section class="panel chart-panel pie-panel">
<div class="section-head">
<h2>分类占比</h2>
<span>环形结构</span>
</div>
<div id="pie-chart" class="chart"></div>
</section>
<section class="panel chart-panel bubble-panel">
<div class="section-head">
<h2>热度分层</h2>
<span>散点气泡</span>
</div>
<div id="bubble-chart" class="chart"></div>
</section>
<section class="panel table-panel">
<div class="section-head">
<h2>热榜明细</h2>
<span id="table-note">按原始顺序保留</span>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>排名</th>
<th>标题</th>
<th>分类</th>
<th>热度</th>
</tr>
</thead>
<tbody id="table-body"></tbody>
</table>
</div>
</section>
</section>
<section class="panel footer">
本页由 `screen_html_export` 生成,适合在系统浏览器中直接打开进行展示。
</section>
</div>
<script>
const defaultPayload = {
"snapshot_id": "template-snapshot",
"generated_at_ms": 0,
"categories": [],
"table": []
}
const themeMeta = {
title: "知乎热榜图表驾驶舱",
renderer: "screen_html_export"
};
const chartColors = ["#62d0ff", "#ecba51", "#6df0c2", "#7f8cff", "#ff8b7e", "#9fcbff", "#58a6ff"];
const charts = {};
function formatNumber(value) {
return new Intl.NumberFormat("zh-CN").format(Number(value || 0));
}
function getTotalHeat(categories) {
return (categories || []).reduce((sum, item) => sum + Number(item.total_heat || 0), 0);
}
function getPeakHeat(table) {
return (table || []).reduce((max, row) => Math.max(max, Number(row.heat_value || 0)), 0);
}
function buildLeadSummary(table, categories) {
const top = (table || [])[0];
const category = (categories || []).slice().sort((a, b) => (b.total_heat || 0) - (a.total_heat || 0))[0];
const parts = [];
if (top) {
parts.push(`榜首是“${top.title}`);
}
if (category) {
parts.push(`主导分类为“${category.category_label}`);
}
parts.push(`共覆盖 ${(table || []).length} 条热点`);
return parts.join("");
}
function ensureCharts() {
if (!window.echarts) {
return;
}
charts.bar = charts.bar || echarts.init(document.getElementById("bar-chart"));
charts.top = charts.top || echarts.init(document.getElementById("top-chart"));
charts.pie = charts.pie || echarts.init(document.getElementById("pie-chart"));
charts.bubble = charts.bubble || echarts.init(document.getElementById("bubble-chart"));
}
function renderBarChart(categories) {
const sorted = (categories || []).slice().sort((a, b) => Number(a.total_heat || 0) - Number(b.total_heat || 0));
charts.bar.setOption({
animationDuration: 700,
grid: {left: 90, right: 18, top: 10, bottom: 20},
xAxis: {
type: "value",
axisLabel: {color: "#8ea6c2"},
splitLine: {lineStyle: {color: "rgba(255,255,255,0.06)"}}
},
yAxis: {
type: "category",
data: sorted.map((item) => item.category_label),
axisLabel: {color: "#eef6ff"},
axisLine: {lineStyle: {color: "rgba(255,255,255,0.1)"}}
},
tooltip: {trigger: "axis", axisPointer: {type: "shadow"}},
series: [{
type: "bar",
data: sorted.map((item, index) => ({
value: Number(item.total_heat || 0),
itemStyle: {color: chartColors[index % chartColors.length], borderRadius: [0, 8, 8, 0]}
})),
label: {show: true, position: "right", color: "#dfeeff"}
}]
});
}
function renderTopChart(table) {
const top = (table || []).slice(0, 10);
charts.top.setOption({
animationDuration: 700,
grid: {left: 42, right: 12, top: 26, bottom: 46},
tooltip: {trigger: "axis", axisPointer: {type: "shadow"}},
xAxis: {
type: "category",
data: top.map((row) => `#${row.rank}`),
axisLabel: {color: "#8ea6c2"},
axisLine: {lineStyle: {color: "rgba(255,255,255,0.1)"}}
},
yAxis: {
type: "value",
axisLabel: {color: "#8ea6c2"},
splitLine: {lineStyle: {color: "rgba(255,255,255,0.06)"}}
},
series: [{
type: "bar",
data: top.map((row, index) => ({
value: Number(row.heat_value || 0),
itemStyle: {color: chartColors[index % chartColors.length], borderRadius: [8, 8, 0, 0]}
})),
label: {show: true, position: "top", color: "#eef6ff", formatter: ({dataIndex}) => top[dataIndex].heat_text}
}]
});
}
function renderPieChart(categories) {
charts.pie.setOption({
animationDuration: 700,
color: chartColors,
tooltip: {trigger: "item"},
legend: {
bottom: 2,
textStyle: {color: "#8ea6c2", fontSize: 11},
itemWidth: 12,
itemHeight: 8
},
series: [{
type: "pie",
radius: ["44%", "72%"],
center: ["50%", "44%"],
itemStyle: {borderColor: "#081a2c", borderWidth: 2},
label: {
color: "#eef6ff",
formatter: "{b}\n{d}%"
},
data: (categories || []).map((item) => ({
name: item.category_label,
value: Number(item.total_heat || 0)
}))
}]
});
}
function renderBubbleChart(table) {
const top = (table || []).slice(0, 12);
charts.bubble.setOption({
animationDuration: 700,
color: chartColors,
grid: {left: 44, right: 18, top: 16, bottom: 36},
xAxis: {
type: "value",
name: "排名",
inverse: true,
min: 0,
max: Math.max(...top.map((row) => Number(row.rank || 0)), 10) + 1,
nameTextStyle: {color: "#8ea6c2"},
axisLabel: {color: "#8ea6c2"},
splitLine: {lineStyle: {color: "rgba(255,255,255,0.06)"}}
},
yAxis: {
type: "value",
name: "热度值",
nameTextStyle: {color: "#8ea6c2"},
axisLabel: {color: "#8ea6c2"},
splitLine: {lineStyle: {color: "rgba(255,255,255,0.06)"}}
},
tooltip: {
formatter: (params) => {
const row = params.data.raw;
return `${row.title}<br/>排名 #${row.rank}<br/>热度 ${row.heat_text}<br/>分类 ${row.category_label}`;
}
},
series: [{
type: "scatter",
symbolSize: (value) => Math.max(18, Math.min(56, value[2] / 80000)),
data: top.map((row, index) => ({
value: [Number(row.rank || 0), Number(row.heat_value || 0), Number(row.heat_value || 0)],
raw: row,
itemStyle: {color: chartColors[index % chartColors.length], opacity: 0.82}
}))
}]
});
}
function renderTable(table) {
document.getElementById("table-body").innerHTML = (table || []).map((row) => `
<tr>
<td class="rank">#${row.rank}</td>
<td>${row.title}</td>
<td><span class="tag">${row.category_label}</span></td>
<td class="heat">${row.heat_text}</td>
</tr>
`).join("");
}
function render(payload) {
const data = payload || defaultPayload;
const categories = data.categories || [];
const table = data.table || [];
document.title = themeMeta.title;
document.getElementById("snapshot-meta").textContent =
`${data.snapshot_id} | 生成时间 ${new Date(data.generated_at_ms || 0).toLocaleString()}`;
document.getElementById("metric-total").textContent = formatNumber(table.length);
document.getElementById("metric-categories").textContent = formatNumber(categories.length);
document.getElementById("metric-heat").textContent = formatNumber(getTotalHeat(categories));
document.getElementById("metric-peak").textContent = formatNumber(getPeakHeat(table));
document.getElementById("lead-summary").textContent = buildLeadSummary(table, categories);
document.getElementById("table-note").textContent =
table.length > 0 ? `当前展示 ${table.length} 条热点` : "暂无热榜数据";
renderTable(table);
ensureCharts();
if (window.echarts) {
renderBarChart(categories);
renderTopChart(table);
renderPieChart(categories);
renderBubbleChart(table);
}
}
window.addEventListener("resize", () => {
Object.values(charts).forEach((chart) => chart && chart.resize());
});
render(defaultPayload);
</script>
</body>
</html>