Преглед изворни кода

feat(mode_workflow): Query 规则 动作×类型 27×50 正交表渲染(tier 底色)

刘文武 пре 1 недеља
родитељ
комит
ce7f7eb75a
1 измењених фајлова са 60 додато и 0 уклоњено
  1. 60 0
      examples/mode_workflow/index.html

+ 60 - 0
examples/mode_workflow/index.html

@@ -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, "&quot;")}">`
+                  + `${q}${badge}</td>`;
+          });
+          html += "</tr>";
+        });
+        html += "</tbody></table>";
+        $("#qr-table-wrap").innerHTML = html;
+      }
+
       /* ════ 新建搜索 ════ */
       /* 渠道下拉多选(选项同 search_eval:小红书/知乎/公众号/抖音/视频号/YouTube) */
       const CHANNELS = [