|
@@ -132,10 +132,40 @@ function setupFloatingApplyToTooltips() {
|
|
|
hideTooltip();
|
|
hideTooltip();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ document.addEventListener('pointerover', (event) => {
|
|
|
|
|
+ const target = event.target.closest('.apply-to-evidence-note[data-excerpt-key]');
|
|
|
|
|
+ if (!target) return;
|
|
|
|
|
+ const row = target.closest('tr');
|
|
|
|
|
+ if (!row) return;
|
|
|
|
|
+ row.querySelectorAll(`.body-excerpt-highlight[data-excerpt-key="${target.dataset.excerptKey}"]`).forEach(el => {
|
|
|
|
|
+ el.classList.add('active');
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ document.addEventListener('pointerout', (event) => {
|
|
|
|
|
+ const target = event.target.closest('.apply-to-evidence-note[data-excerpt-key]');
|
|
|
|
|
+ if (!target || target.contains(event.relatedTarget)) return;
|
|
|
|
|
+ const row = target.closest('tr');
|
|
|
|
|
+ if (!row) return;
|
|
|
|
|
+ row.querySelectorAll(`.body-excerpt-highlight[data-excerpt-key="${target.dataset.excerptKey}"]`).forEach(el => {
|
|
|
|
|
+ el.classList.remove('active');
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
window.addEventListener('scroll', hideTooltip, true);
|
|
window.addEventListener('scroll', hideTooltip, true);
|
|
|
window.addEventListener('resize', hideTooltip);
|
|
window.addEventListener('resize', hideTooltip);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function makeExcerptKey(text) {
|
|
|
|
|
+ let hash = 0;
|
|
|
|
|
+ const str = String(text || '');
|
|
|
|
|
+ for (let i = 0; i < str.length; i += 1) {
|
|
|
|
|
+ hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
|
|
|
+ hash |= 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ return `e${Math.abs(hash)}`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function getWorkflowGroups(item) {
|
|
function getWorkflowGroups(item) {
|
|
|
if (!item || !Array.isArray(item.workflow_groups)) return [];
|
|
if (!item || !Array.isArray(item.workflow_groups)) return [];
|
|
|
return item.workflow_groups.filter(group => group && typeof group === 'object');
|
|
return item.workflow_groups.filter(group => group && typeof group === 'object');
|
|
@@ -2608,6 +2638,19 @@ window.renderStructuredData = function (items, type, parentItem = null) {
|
|
|
</div>
|
|
</div>
|
|
|
`;
|
|
`;
|
|
|
};
|
|
};
|
|
|
|
|
+ const renderEvidence = (pathObj) => {
|
|
|
|
|
+ if (typeof pathObj !== 'object' || pathObj === null) return '';
|
|
|
|
|
+ if (!('body_excerpt' in pathObj) && !('body_excerpt_note' in pathObj)) return '';
|
|
|
|
|
+ const excerpt = pathObj.body_excerpt || '';
|
|
|
|
|
+ const note = pathObj.body_excerpt_note || '';
|
|
|
|
|
+ const excerptKey = excerpt ? makeExcerptKey(excerpt) : '';
|
|
|
|
|
+ return `<div class="apply-to-evidence">
|
|
|
|
|
+ <div class="apply-to-evidence-row">
|
|
|
|
|
+ <span class="apply-to-evidence-label">关联做法</span>
|
|
|
|
|
+ <span class="apply-to-evidence-value apply-to-evidence-note ${note ? '' : 'empty'}" ${excerptKey ? `data-excerpt-key="${excerptKey}"` : ''}>${note ? escapeApplyToText(note) : '空'}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>`;
|
|
|
|
|
+ };
|
|
|
let res = '<div style="display:flex; flex-direction:column; gap:6px;">';
|
|
let res = '<div style="display:flex; flex-direction:column; gap:6px;">';
|
|
|
let hasRows = false;
|
|
let hasRows = false;
|
|
|
Object.entries(valObj).forEach(([k, v]) => {
|
|
Object.entries(valObj).forEach(([k, v]) => {
|
|
@@ -2637,7 +2680,8 @@ window.renderStructuredData = function (items, type, parentItem = null) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (htmlParts) {
|
|
if (htmlParts) {
|
|
|
- res += `<span class="apply-to-path-item has-tooltip">${htmlParts}${tooltipHtml}</span>`;
|
|
|
|
|
|
|
+ const evidenceHtml = renderEvidence(pathObj);
|
|
|
|
|
+ res += `<span class="apply-to-path-block"><span class="apply-to-path-item ${tooltipHtml ? 'has-tooltip' : ''}">${htmlParts}${tooltipHtml}</span>${evidenceHtml}</span>`;
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -2657,7 +2701,8 @@ window.renderStructuredData = function (items, type, parentItem = null) {
|
|
|
const pathStr = typeof item === 'object' && item !== null ? item.path : String(item || '');
|
|
const pathStr = typeof item === 'object' && item !== null ? item.path : String(item || '');
|
|
|
const tooltipHtml = renderTooltip(item);
|
|
const tooltipHtml = renderTooltip(item);
|
|
|
if (pathStr) {
|
|
if (pathStr) {
|
|
|
- res += `<span class="apply-to-path-item ${tooltipHtml ? 'has-tooltip' : ''}" style="border: 2px dashed #94a3b8; background: transparent;">${renderPathParts(pathStr, true)}${tooltipHtml}</span>`;
|
|
|
|
|
|
|
+ const evidenceHtml = renderEvidence(item);
|
|
|
|
|
+ res += `<span class="apply-to-path-block"><span class="apply-to-path-item ${tooltipHtml ? 'has-tooltip' : ''}" style="border: 2px dashed #94a3b8; background: transparent;">${renderPathParts(pathStr, true)}${tooltipHtml}</span>${evidenceHtml}</span>`;
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
res += `</div></div>`;
|
|
res += `</div></div>`;
|
|
@@ -2929,9 +2974,47 @@ window.renderStructuredData = function (items, type, parentItem = null) {
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
const matchedCapabilities = new Set();
|
|
const matchedCapabilities = new Set();
|
|
|
|
|
+ const collectBodyExcerpts = (applyTo, suggestApplyTo) => {
|
|
|
|
|
+ const excerpts = [];
|
|
|
|
|
+ const addExcerpt = (entry) => {
|
|
|
|
|
+ if (entry && typeof entry === 'object' && typeof entry.body_excerpt === 'string' && entry.body_excerpt.trim()) {
|
|
|
|
|
+ excerpts.push(entry.body_excerpt.trim());
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ if (applyTo && typeof applyTo === 'object') {
|
|
|
|
|
+ Object.values(applyTo).forEach(items => {
|
|
|
|
|
+ if (Array.isArray(items)) items.forEach(addExcerpt);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ if (Array.isArray(suggestApplyTo)) {
|
|
|
|
|
+ suggestApplyTo.forEach(addExcerpt);
|
|
|
|
|
+ }
|
|
|
|
|
+ return [...new Set(excerpts)].sort((a, b) => b.length - a.length);
|
|
|
|
|
+ };
|
|
|
|
|
+ const renderBodyWithExcerptHighlights = (body, applyTo, suggestApplyTo) => {
|
|
|
|
|
+ if (!body) return '-';
|
|
|
|
|
+ const text = String(body);
|
|
|
|
|
+ const excerpts = collectBodyExcerpts(applyTo, suggestApplyTo);
|
|
|
|
|
+ if (excerpts.length === 0) return escapeHtml(text);
|
|
|
|
|
+
|
|
|
|
|
+ let out = '';
|
|
|
|
|
+ let i = 0;
|
|
|
|
|
+ while (i < text.length) {
|
|
|
|
|
+ const match = excerpts.find(excerpt => text.startsWith(excerpt, i));
|
|
|
|
|
+ if (match) {
|
|
|
|
|
+ out += `<span class="body-excerpt-highlight" data-excerpt-key="${makeExcerptKey(match)}">${escapeHtml(match)}</span>`;
|
|
|
|
|
+ i += match.length;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ out += escapeHtml(text[i]);
|
|
|
|
|
+ i += 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return out;
|
|
|
|
|
+ };
|
|
|
const renderCapabilityColumns = (capability) => {
|
|
const renderCapabilityColumns = (capability) => {
|
|
|
const applyTo = capability && (capability.apply_to_draft || capability.apply_to_grounding || capability.apply_to);
|
|
const applyTo = capability && (capability.apply_to_draft || capability.apply_to_grounding || capability.apply_to);
|
|
|
const suggestApplyTo = capability && capability.apply_to_draft ? null : capability && capability.suggest_apply_to;
|
|
const suggestApplyTo = capability && capability.apply_to_draft ? null : capability && capability.suggest_apply_to;
|
|
|
|
|
+ const bodyHtml = capability && capability.body ? renderBodyWithExcerptHighlights(capability.body, applyTo, suggestApplyTo) : '-';
|
|
|
return `
|
|
return `
|
|
|
<td class="capability-cell" style="font-family: monospace;">
|
|
<td class="capability-cell" style="font-family: monospace;">
|
|
|
<span class="row-expand-icon">▶</span>
|
|
<span class="row-expand-icon">▶</span>
|
|
@@ -2941,7 +3024,7 @@ window.renderStructuredData = function (items, type, parentItem = null) {
|
|
|
<td class="capability-cell">${renderAction(capability)}</td>
|
|
<td class="capability-cell">${renderAction(capability)}</td>
|
|
|
<td class="capability-cell">${capability && capability.outputs && capability.outputs.length > 0 ? renderDataObjList(capability.outputs) : '-'}</td>
|
|
<td class="capability-cell">${capability && capability.outputs && capability.outputs.length > 0 ? renderDataObjList(capability.outputs) : '-'}</td>
|
|
|
<td class="capability-cell" style="font-size:0.9em;"><div class="capability-clamp apply-to-clamp">${applyTo ? renderApplyToVal(applyTo, suggestApplyTo) : '-'}</div></td>
|
|
<td class="capability-cell" style="font-size:0.9em;"><div class="capability-clamp apply-to-clamp">${applyTo ? renderApplyToVal(applyTo, suggestApplyTo) : '-'}</div></td>
|
|
|
- <td class="capability-cell"><div class="capability-clamp capability-text">${capability && capability.body ? escapeHtml(capability.body) : '-'}</div></td>
|
|
|
|
|
|
|
+ <td class="capability-cell"><div class="capability-clamp capability-text">${bodyHtml}</div></td>
|
|
|
<td class="capability-cell"><div class="capability-clamp">${capability ? renderEffects(capability.effects) : '-'}</div></td>
|
|
<td class="capability-cell"><div class="capability-clamp">${capability ? renderEffects(capability.effects) : '-'}</div></td>
|
|
|
<td class="capability-cell"><div class="capability-clamp">${capability ? renderTools(capability.tools) : '-'}</div></td>
|
|
<td class="capability-cell"><div class="capability-clamp">${capability ? renderTools(capability.tools) : '-'}</div></td>
|
|
|
`;
|
|
`;
|