Semantic_Mismatch / app /web /custom.js
VirVen's picture
Upload folder using huggingface_hub
2271a07 verified
() => {
/* ===== Shadow DOM helpers (Gradio) ===== */
const appEl = () => document.querySelector("gradio-app");
const root = () => (appEl() && appEl().shadowRoot) ? appEl().shadowRoot : document;
const q = (sel) => root().querySelector(sel);
const qa = (sel) => root().querySelectorAll(sel);
/* Which left paragraph (pair) is focused + which left spans must stay bright */
const current = {
idx: null,
keepIds: [], // LEFT keep-bright span IDs
dimLeftAll: false, // remember “dim all left spans” across repaints
anchorIds: [], // LEFT anchors to re-bracket after repaint
keepRightIds: [], // RIGHT keep-bright span IDs
anchorRightIds: [], // RIGHT anchors to re-bracket after repaint
selectedLeftId: null, // persist selection across repaint
selectedRightId: null
};
/* ===== Visual helpers (hover + selection) ===== */
const confBox = () => q('#conf_box');
const hi = (id, col) => {
const e = q('#' + CSS.escape(id));
if (!e) return;
if (e.classList.contains('selected')) return; // keep selection strong
if (!('_bg' in e.dataset)) e.dataset._bg = e.style.backgroundColor || '';
// Only apply color if provided; passing null keeps current background
if (col != null) e.style.backgroundColor = col || e.style.backgroundColor;
e.style.outline = '2px solid #000';
};
const bye = (id) => {
const e = q('#' + CSS.escape(id));
if (!e) return;
if (e.classList.contains('selected')) return;
const bg = ('_bg' in e.dataset) ? e.dataset._bg : '';
e.style.backgroundColor = bg || '';
e.style.outline = '';
};
const clearSelection = () => {
qa('span.hl.selected, span.hl.dimmed, span.hl.anchor-hl').forEach(el => {
el.classList.remove('selected','dimmed','anchor-hl');
el.style.removeProperty('--anchor-color');
});
current.selectedLeftId = null;
current.selectedRightId = null;
qa('span.hl').forEach(el => {
const bg = ('_bg' in el.dataset) ? el.dataset._bg : '';
el.style.backgroundColor = bg || '';
el.style.outline = '';
});
if (confBox()) confBox().textContent = 'Confidence: -';
};
/* Color + bracket an anchor with its own label color (or mate color) */
function forceAnchor(el){
if (!el) return;
const col = el.dataset.selfcolor || el.dataset.hcolor || '#16a34a';
el.style.setProperty('--anchor-color', col);
el.classList.add('anchor-hl');
}
/* span dimming in LEFT pane */
function dimLeftSpansExcept(keepIds = []) {
const keep = new Set(keepIds);
qa('#left_pane .hl').forEach(el => {
if (keep.has(el.id)) {
el.classList.remove('dimmed');
} else {
el.classList.add('dimmed');
}
});
}
function clearLeftSpanDimming(){
qa('#left_pane .hl.dimmed').forEach(el => el.classList.remove('dimmed'));
}
/* span dimming in RIGHT pane */
function dimRightSpansExcept(keepIds = []) {
const keep = new Set(keepIds);
qa('#right_pane .hl').forEach(el => {
if (keep.has(el.id)) {
el.classList.remove('dimmed');
} else {
el.classList.add('dimmed');
}
});
}
function clearRightSpanDimming(){
qa('#right_pane .hl.dimmed').forEach(el => el.classList.remove('dimmed'));
}
/* ===== Paragraph dimming (left pane) ===== */
function dimParagraphs(idx){
if (idx == null) return;
qa('#left_pane .para-box').forEach(pb => {
const p = pb.getAttribute('data-idx');
if (p === String(idx)) {
pb.classList.add('para-focus');
pb.classList.remove('para-dim');
} else {
pb.classList.remove('para-focus');
pb.classList.add('para-dim');
}
});
}
function clearParagraphDimming(){
qa('#left_pane .para-box').forEach(pb => pb.classList.remove('para-dim','para-focus'));
}
/* Reapply paragraph + span dimming after Gradio re-renders the left HTML */
(function observeLeftRepaints(){
const L = q('#left_pane');
if (!L || !('MutationObserver' in window)) return;
const obs = new MutationObserver(() => {
setTimeout(() => {
if (current.idx != null) dimParagraphs(current.idx);
if (current.keepIds && current.keepIds.length){
dimLeftSpansExcept(current.keepIds);
} else if (current.dimLeftAll) {
qa('#left_pane .hl').forEach(el => el.classList.add('dimmed'));
}
// Always re-apply brackets on known anchors after repaint
(current.anchorIds || []).forEach(id => {
const el = q('#' + CSS.escape(id));
if (el) forceAnchor(el);
});
// Re-select the previously selected LEFT span if any
if (current.selectedLeftId) {
const el = q('#' + CSS.escape(current.selectedLeftId));
if (el) {
el.classList.add('selected');
el.classList.remove('dimmed');
el.style.outline = '2px solid #000';
}
}
}, 0);
});
obs.observe(L, { childList: true, subtree: true, characterData: true });
})();
/* Reapply RIGHT dimming/anchors after right HTML repaints */
(function observeRightRepaints(){
const R = q('#right_pane');
if (!R || !('MutationObserver' in window)) return;
const obs = new MutationObserver(() => {
setTimeout(() => {
if (current.keepRightIds && current.keepRightIds.length){
dimRightSpansExcept(current.keepRightIds);
}
(current.anchorRightIds || []).forEach(id => {
const el = q('#' + CSS.escape(id));
if (el) forceAnchor(el);
});
if (current.selectedRightId) {
const el = q('#' + CSS.escape(current.selectedRightId));
if (el) {
el.classList.add('selected');
el.classList.remove('dimmed');
el.style.outline = '2px solid #000';
}
}
}, 0);
});
obs.observe(R, { childList: true, subtree: true, characterData: true });
})();
/* ===== Bridge to Python (hidden textbox) ===== */
function findBridgeBox(){
return q('#bridge_click textarea, #bridge_click input');
}
function sendBridge(val){
const box = findBridgeBox();
if (!box) return false;
box.value = val;
box.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
return true;
}
/* ===== Hover feedback (cross-highlight left/right) ===== */
root().addEventListener('mouseover', ev => {
const t = (ev.composedPath && ev.composedPath()[0]) || ev.target;
const s = t && t.closest ? t.closest('span.hl') : null;
if (s) {
const tgt = s.dataset.target || '';
if (s.dataset.kind === 'addition') {
// New behavior: pair ONLY with another addition if present; never with anchors.
const mateId = s.dataset.rmate || s.dataset.lmate || '';
// Keep self as-is (no green/red wash), outline only:
hi(s.id, null);
// If there is an explicit addition mate, outline it too; otherwise do nothing cross-panel
if (mateId) hi(mateId, null);
} else {
// Non-additions keep normal cross-highlight
hi(s.id, s.dataset.hcolor || '');
if (tgt) hi(tgt, s.dataset.hcolor || '');
}
const c = parseFloat(s.dataset.conf || '');
if (confBox()) confBox().textContent = 'Confidence: ' + (isNaN(c) ? '-' : c.toFixed(3));
}
}, {capture:true});
root().addEventListener('mouseout', ev => {
const t = (ev.composedPath && ev.composedPath()[0]) || ev.target;
const s = t && t.closest ? t.closest('span.hl') : null;
if (s) {
const tgt = s.dataset.target || '';
bye(s.id);
if (s.dataset.kind === 'addition') {
const mateId = s.dataset.rmate || s.dataset.lmate || '';
if (mateId) bye(mateId);
} else {
if (tgt) bye(tgt);
}
}
}, {capture:true});
/* ===== Click handling (no scrolling, no floating) ===== */
root().addEventListener('click', function (e) {
const path = e.composedPath ? e.composedPath() : [e.target];
let target = null;
for (const n of path) {
if (n instanceof Element && n.matches && (n.matches('.hl') || n.matches('.para-box'))) {
target = n; break;
}
}
// A) Click on a highlighted LEFT span
const span = target && target.matches('.hl') ? target : null;
if (span && span.hasAttribute('data-pair') && span.hasAttribute('data-left')) {
e.preventDefault();
e.stopPropagation();
// Toggle: if user clicks the same already-selected span → clear selection and state
if (span.classList.contains('selected') &&
((span.hasAttribute('data-left') && span.id === current.selectedLeftId) ||
(span.hasAttribute('data-right') && span.id === current.selectedRightId))) {
const pidx = parseInt(span.getAttribute('data-pair') || '0', 10);
clearSelection();
clearLeftSpanDimming();
clearRightSpanDimming();
current.keepIds = [];
current.keepRightIds = [];
current.anchorIds = [];
current.anchorRightIds = [];
current.dimLeftAll = false;
// Re-render paragraph in neutral state
sendBridge(`P:${pidx}`);
e.stopImmediatePropagation();
return;
}
clearSelection(); // only clear span selection; dimming handled below
span.classList.add('selected');
const mateId = span.dataset.target;
// no fill on select — keep only the box
span.style.outline = '2px solid #000';
current.selectedLeftId = span.id;
current.selectedRightId = mateId || null;
if (mateId) {
const mate = q('#' + CSS.escape(mateId));
if (mate) {
mate.classList.add('selected');
mate.style.outline = '2px solid #000';
mate.classList.remove('dimmed'); // ensure it is not dimmed on first paint
// If LEFT click was on an "addition" span, force its mate (anchor) to green
if (span.dataset.kind === 'addition') forceAnchor(mate);
}
}
// RIGHT dimming: keep mate + right in-pane anchor if any
const keepRight = [];
if (mateId) keepRight.push(mateId); // the right *anchor* (target)
if (span.dataset.kind === 'addition' && span.dataset.ranchor)
keepRight.push(span.dataset.ranchor); // in-pane anchor (same as target in most cases)
if (span.dataset.kind === 'addition' && span.dataset.rmate)
keepRight.push(span.dataset.rmate); // the right *addition mate* — keep it undimmed (blue)
dimRightSpansExcept(keepRight);
current.keepRightIds = keepRight.slice();
current.anchorRightIds = [];
if (span.dataset.kind === 'addition') {
if (span.dataset.ranchor) current.anchorRightIds.push(span.dataset.ranchor);
}
// build "keep bright" set for left spans (clicked + its left-anchor if addition)
const keep = [span.id];
if (span.dataset.kind === 'addition' && span.dataset.lanchor) {
keep.push(span.dataset.lanchor);
// also green the left in-pane anchor itself
const lEl = q('#' + CSS.escape(span.dataset.lanchor));
if (lEl) lEl.classList.add('anchor-hl');
}
// Dim all other LEFT spans + remember this set for re-renders
dimLeftSpansExcept(keep);
current.keepIds = keep.slice();
current.anchorIds = [];
if (span.dataset.kind === 'addition') {
if (span.dataset.lanchor) current.anchorIds.push(span.dataset.lanchor);
if (span.dataset.selfanchor === '1') current.anchorIds.push(span.id);
}
// force-highlight the LEFT in-pane anchor when present
if (span.dataset.kind === 'addition') {
const la = span.dataset.lanchor;
if (la) forceAnchor(q('#' + CSS.escape(la)));
// And the RIGHT in-pane anchor when present (if we clicked a left span and it embeds right anchors too)
const ra = span.dataset.ranchor;
if (ra) forceAnchor(q('#' + CSS.escape(ra)));
// Self-anchored: also bracket the clicked span itself to “green with brackets”
if (span.dataset.selfanchor === '1') {
forceAnchor(span);
}
}
const pidx = parseInt(span.getAttribute('data-pair') || '0', 10);
const lidx = span.getAttribute('data-left');
// apply paragraph dimming immediately for snappy feel
current.idx = pidx;
dimParagraphs(current.idx);
if (sendBridge(`S:${pidx}:${lidx}`)) {
// no-op; already dimmed above
}
e.stopImmediatePropagation();
return;
}
// C) Click on a highlighted RIGHT span
if (span && span.hasAttribute('data-pair') && span.hasAttribute('data-right')) {
e.preventDefault();
e.stopPropagation();
// Toggle: if user clicks the same already-selected span → clear selection and state
if (span.classList.contains('selected') &&
((span.hasAttribute('data-left') && span.id === current.selectedLeftId) ||
(span.hasAttribute('data-right') && span.id === current.selectedRightId))) {
const pidx = parseInt(span.getAttribute('data-pair') || '0', 10);
clearSelection();
clearLeftSpanDimming();
clearRightSpanDimming();
current.keepIds = [];
current.keepRightIds = [];
current.anchorIds = [];
current.anchorRightIds = [];
current.dimLeftAll = false;
// Re-render paragraph in neutral state
sendBridge(`P:${pidx}`);
e.stopImmediatePropagation();
return;
}
clearSelection();
span.classList.add('selected');
span.style.outline = '2px solid #000';
current.selectedRightId = span.id;
const mateId = span.dataset.target; // points to L-k-li
// Fallback: for additions without data-target, try left anchor
const laFallback = span.dataset.lanchor || '';
current.selectedLeftId = mateId || null;
if (mateId) {
const mate = q('#' + CSS.escape(mateId));
if (mate) {
mate.classList.add('selected');
mate.style.outline = '2px solid #000';
mate.classList.remove('dimmed'); // guarantee not dimmed on first click
// If RIGHT click was on an "addition" span, force its mate (anchor) to green
if (span.dataset.kind === 'addition') forceAnchor(mate);
}
}
// RIGHT dimming: keep selected right span + its right in-pane anchor (if any)
const keepR = [span.id];
if (span.dataset.kind === 'addition' && span.dataset.ranchor) keepR.push(span.dataset.ranchor);
dimRightSpansExcept(keepR);
current.keepRightIds = keepR.slice();
// default: we are NOT in “dim all left” mode unless proven otherwise below
current.dimLeftAll = false;
current.anchorRightIds = [];
if (span.dataset.kind === 'addition') {
if (span.dataset.ranchor) current.anchorRightIds.push(span.dataset.ranchor);
// Self-anchored: bracket the clicked span itself too
if (span.dataset.selfanchor === '1') {
forceAnchor(span);
current.anchorRightIds.push(span.id);
}
}
// dim all other LEFT spans, keep only the left anchor (and/or left mate) bright
const keep = [];
if (mateId) keep.push(mateId);
// try anchor fallback for additions
if (!mateId && laFallback) {
keep.push(laFallback);
current.selectedLeftId = laFallback;
}
if (span.dataset.kind === 'addition' && span.dataset.lanchor) {
keep.push(span.dataset.lanchor);
}
if (keep.length) {
dimLeftSpansExcept(keep);
current.dimLeftAll = false;
} else {
// No known left mate yet → do NOT pre-dim; wait for Python to resolve a mate.
clearLeftSpanDimming();
current.dimLeftAll = false;
}
current.keepIds = keep.slice(); // always refresh keepIds
// persist left anchor so we can re-apply brackets after re-render
current.anchorIds = [];
if (span.dataset.kind === 'addition') {
const la = span.dataset.lanchor || mateId;
if (la) {
current.anchorIds = [la];
const lEl = q('#' + CSS.escape(la));
if (lEl) forceAnchor(lEl);
}
}
// force-highlight the RIGHT in-pane anchor (anchor claim on the same B paragraph)
if (span.dataset.kind === 'addition') {
const ra = span.dataset.ranchor;
if (ra) forceAnchor(q('#' + CSS.escape(ra)));
// And the LEFT in-pane anchor (when addition is on the right)
const la = span.dataset.lanchor;
if (la) forceAnchor(q('#' + CSS.escape(la)));
}
const pidx = parseInt(span.getAttribute('data-pair') || '0', 10);
const ridx = span.getAttribute('data-right');
// apply paragraph dimming immediately; prevents “first click looks dimmed”
current.idx = pidx;
dimParagraphs(current.idx);
if (sendBridge(`R:${pidx}:${ridx}`)) {
// already set above
}
e.stopImmediatePropagation();
return;
}
// B) Click on a LEFT paragraph card (anywhere on the card)
const para = target && target.matches('.para-box') ? target : null;
if (para && para.hasAttribute('data-idx')) {
const idx = parseInt(para.getAttribute('data-idx') || '0', 10);
clearSelection(); // ensure boxes/brackets from the last selection are removed immediately
if (sendBridge(`P:${idx}`)) {
current.idx = idx;
dimParagraphs(current.idx); // only dim, no scroll, no float
// Clear span dimming if user clicks the card background
clearLeftSpanDimming();
clearRightSpanDimming();
current.keepIds = [];
current.keepRightIds = [];
current.dimLeftAll = false; // reset
current.anchorIds = [];
current.anchorRightIds = [];
current.selectedLeftId = null;
current.selectedRightId = null;
}
e.stopImmediatePropagation();
return;
}
}, {capture:true});
/* Clear dimming only when clicking outside BOTH panes (and not on a claim) */
root().addEventListener('click', function (e) {
const left = q('#left_pane');
const right = q('#right_pane');
if (!left) return;
const t = (e.composedPath && e.composedPath()[0]) || e.target;
// If the click is inside either pane or on a claim span, don't clear.
if ((left.contains(t) || (right && right.contains(t))) ||
(t.closest && t.closest('span.hl'))) {
return;
}
// Clicked outside the viewer → clear everything
current.idx = null;
clearParagraphDimming();
clearLeftSpanDimming();
clearRightSpanDimming();
current.keepIds = [];
current.keepRightIds = [];
current.dimLeftAll = false; // reset on outside click
current.anchorIds = [];
current.anchorRightIds = [];
}, {capture:true});
}