|
|
@@ -2074,6 +2074,16 @@
|
|
|
.chip { padding:4px 13px; border-radius:18px; font-size:12.5px; cursor:pointer; border:1.5px solid #e2e2e2; background:#fff; color:#666; }
|
|
|
.chip:hover { border-color:#bbb; color:#333; }
|
|
|
.chip.on { background:#1a1a1a; border-color:#1a1a1a; color:#fff; }
|
|
|
+ .qr-tab { border-collapse:collapse; font-size:11px; }
|
|
|
+ .qr-tab th, .qr-tab td { border:1px solid #eee; padding:3px 6px; white-space:nowrap; }
|
|
|
+ .qr-tab thead th { position:sticky; top:0; background:#f7f7f7; z-index:2; }
|
|
|
+ .qr-corner { position:sticky; left:0; z-index:3; background:#f0f0f0; }
|
|
|
+ .qr-th { position:sticky; left:0; background:#fafafa; z-index:1; text-align:left; }
|
|
|
+ .qr-tl1 { background:#f3f4f6; font-weight:600; color:#444; }
|
|
|
+ .qr-c.t0 { background:#fafafa; color:#bbb; } .qr-c.t1 { background:#f1f8f1; }
|
|
|
+ .qr-c.t2 { background:#dcecdc; } .qr-c.t3 { background:#bfe0bf; }
|
|
|
+ .qr-c.dead { color:#ccc; } .qr-c.keep { outline:2px solid #2563eb; outline-offset:-2px; cursor:pointer; font-weight:600; }
|
|
|
+ .qr-bd { display:inline-block; margin-left:4px; font-style:normal; font-size:9px; color:#2563eb; }
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
@@ -3723,6 +3733,56 @@
|
|
|
renderQrTable();
|
|
|
}
|
|
|
|
|
|
+ // 单格 query = [工具类型] 动作叶 类型叶 [模态] [后缀](与后端 query_score._build_cells 一致)
|
|
|
+ function qrCellQuery(action, type) {
|
|
|
+ const f = qrState.flat;
|
|
|
+ return [f.tool_type, action, type, f.modality, f.suffix].filter((x) => x && x !== "无").join(" ");
|
|
|
+ }
|
|
|
+ function renderQrTable() {
|
|
|
+ const m = qrState.matrix;
|
|
|
+ if (!m) return;
|
|
|
+ const { actions, types, matrix } = m;
|
|
|
+ const sc = qrState.scores; // {"ai_ti":{keep,score,rewrite,...}}
|
|
|
+ // 表头:动作按 l1 分组(参考 Image #2,列头两行:l1 跨列 + 叶子)
|
|
|
+ let html = '<table class="qr-tab"><thead>';
|
|
|
+ // l1 行
|
|
|
+ html += '<tr><th class="qr-corner" rowspan="2">类型 \\ 动作</th>';
|
|
|
+ const groups = [];
|
|
|
+ actions.forEach((a, ai) => {
|
|
|
+ const last = groups[groups.length - 1];
|
|
|
+ if (last && last.l1 === a.l1) last.span++;
|
|
|
+ else groups.push({ l1: a.l1, span: 1, start: ai });
|
|
|
+ });
|
|
|
+ groups.forEach((g) => { html += `<th colspan="${g.span}" class="qr-gh">${g.l1}</th>`; });
|
|
|
+ html += "</tr><tr>";
|
|
|
+ actions.forEach((a) => { html += `<th class="qr-ah">${a.name}</th>`; });
|
|
|
+ html += "</tr></thead><tbody>";
|
|
|
+ // 行:类型按 l1 分组,组首插一行组标题
|
|
|
+ let curL1 = null;
|
|
|
+ types.forEach((t, ti) => {
|
|
|
+ if (t.l1 !== curL1) {
|
|
|
+ curL1 = t.l1;
|
|
|
+ html += `<tr><td class="qr-tl1" colspan="${actions.length + 1}">${t.l1}</td></tr>`;
|
|
|
+ }
|
|
|
+ html += `<tr><th class="qr-th">${t.name}</th>`;
|
|
|
+ actions.forEach((a, ai) => {
|
|
|
+ const cell = matrix[ai][ti] || {};
|
|
|
+ const tier = cell.tier || 0;
|
|
|
+ const q = qrCellQuery(a.name, t.name);
|
|
|
+ const v = sc ? sc[`${ai}_${ti}`] : null;
|
|
|
+ const keep = v && v.keep;
|
|
|
+ const cls = `qr-c t${tier}` + (tier === 0 ? " dead" : "") + (keep ? " keep" : "");
|
|
|
+ const badge = keep && v.score != null ? `<i class="qr-bd">${v.score}</i>` : "";
|
|
|
+ const title = v ? `${v.reason || ""} (n${v.natural}/f${v.findable}/u${v.useful})` : `tier ${tier}`;
|
|
|
+ html += `<td class="${cls}" data-ai="${ai}" data-ti="${ti}" title="${title.replace(/"/g, """)}">`
|
|
|
+ + `${q}${badge}</td>`;
|
|
|
+ });
|
|
|
+ html += "</tr>";
|
|
|
+ });
|
|
|
+ html += "</tbody></table>";
|
|
|
+ $("#qr-table-wrap").innerHTML = html;
|
|
|
+ }
|
|
|
+
|
|
|
/* ════ 新建搜索 ════ */
|
|
|
/* 渠道下拉多选(选项同 search_eval:小红书/知乎/公众号/抖音/视频号/YouTube) */
|
|
|
const CHANNELS = [
|