Files
DemonGatanjieu-Anomalous_Mo…/web/modules/ui_translation_peek.js
T
DemonGatanjieuandClaude Opus 5.5 0abfd0a775 feat(combos): node names, prompt translation peek, multi-model nodes
Each slot names its node (and node pack when not ComfyUI's own). A small
"译" button beside a prompt shows its translation in a read-only panel
(ui_translation_peek.js). On nodes with several models, saved values go
to the row with the same number (strength_02 under lora_02) and more
than four fold behind "+N"; empty optional rows (None) start unticked in
the save dialog so a ten-row LoRA stack does not make ten empty slots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 15:50:58 +08:00

99 lines
4.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* A quick look at a prompt's translation: a small read-only panel beside a button (the combo
* editor's "译"). Chinese goes to English, English to Chinese on a Chinese page. Prompt Studio
* is where prompts are changed; this only shows.
*/
import { translate as t } from './locales.js';
import { needsEnglish, translatePromptText } from './translation_service.js';
function el(tag, className, text) {
const node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
}
function button(className, label, onClick) {
const node = el('button', className, label);
node.type = 'button';
node.onclick = onClick;
return node;
}
/** The language a prompt is shown in: not English → English; English → Chinese on a Chinese page; else none. */
export function translationTarget(text) {
if (needsEnglish(text)) return 'en';
return window.anomalous_browser_lang === 'zh' ? 'zh-CN' : '';
}
let closeTranslation = null;
/** The nearest box around `node` that scrolls. */
function scrollerOf(node) {
for (let at = node.parentElement; at; at = at.parentElement) {
if (/(auto|scroll)/.test(getComputedStyle(at).overflowY) && at.scrollHeight > at.clientHeight) return at;
}
return null;
}
/** A small read-only panel beside `anchor` with the translation of `text`; another press, Esc or a click elsewhere closes it. */
export async function showTranslation(anchor, text) {
const wasOpen = closeTranslation?.anchor === anchor;
closeTranslation?.();
if (wasOpen) return;
const target = translationTarget(text);
const panel = el('div', 'anomalous-structure-translation');
const head = el('div', 'anomalous-structure-translation-head');
const body = el('div', 'anomalous-structure-translation-text', t('comboTranslating'));
const copy = button('anomalous-scan-row-btn', t('comboTranslationCopy'), () => {
void navigator.clipboard?.writeText(body.textContent).then(() => { copy.textContent = t('comboTranslationCopied'); }, () => {});
});
copy.disabled = true;
head.append(el('strong', '', t(target === 'en' ? 'comboTranslationToEnglish' : 'comboTranslationToChinese')), copy,
button('anomalous-structure-translation-close', '×', () => close()));
panel.append(head, body);
document.body.append(panel);
// To the right of the button when there is room, else under it, kept on screen.
const at = anchor.getBoundingClientRect();
const width = Math.min(360, window.innerWidth - 16);
const left = at.right + 8 + width <= window.innerWidth - 8 ? at.right + 8 : Math.max(8, at.right - width);
panel.style.width = `${width}px`;
panel.style.left = `${left}px`;
panel.style.top = `${Math.max(8, Math.min(left > at.right ? at.top : at.bottom + 6, window.innerHeight - 220))}px`;
const controller = new AbortController();
const onDown = (event) => { if (!panel.contains(event.target) && event.target !== anchor) close(); };
// Only the page under the button moving closes it (it would stay put); other panels may scroll on their own.
const scroller = scrollerOf(anchor);
const onScroll = () => close();
const onKey = (event) => {
if (event.key !== 'Escape') return;
event.stopPropagation();
close();
};
const close = () => {
controller.abort();
panel.remove();
document.removeEventListener('pointerdown', onDown, true);
document.removeEventListener('keydown', onKey, true);
scroller?.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', onScroll);
anchor.classList.remove('is-active');
if (closeTranslation === close) closeTranslation = null;
};
close.anchor = anchor;
closeTranslation = close;
anchor.classList.add('is-active');
document.addEventListener('pointerdown', onDown, true);
document.addEventListener('keydown', onKey, true);
scroller?.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onScroll);
const result = await translatePromptText(text, { targetLang: target, signal: controller.signal });
if (result.cancelled || !panel.isConnected) return;
body.textContent = result.ok ? result.translated : t('comboTranslationFailed');
body.classList.toggle('is-bad', !result.ok);
copy.disabled = !result.ok;
}