Files
DemonGatanjieu-Anomalous_Mo…/web/modules/ui_prompt_composer.js
T
DemonGatanjieuandClaude Opus 5.5 96736560af feat(node): translate prompts on the node; the separate translator goes
Current node lists the selected node's prompt boxes (prompt_boxes.js, so
each is marked positive or negative by its wiring). Chinese text gets
"translate to English", written back only if the box still holds the same
text when the translation arrives (one undo); English text can show its
Chinese meaning without writing.

The standalone Prompt Translator (ui_prompt_translator.js, 838 lines, its
four fallback ways of guessing a text box, its styles and its toolbox
entry) is removed, with Prompt Studio's appendPromptToStudio that only it
used. Prompt Studio keeps its own translate buttons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 06:14:05 +08:00

125 lines
5.0 KiB
JavaScript

import { createViewScope, bindDrawerResize } from './ui_lifecycle.js';
import { createPromptWorkbench } from './ui_prompt_workbench.js';
import { newDraft } from './prompt_studio_data.js';
import { planToWorkbenchDraft } from './prompt_composition.js';
import { anomalousAlert, anomalousConfirm } from './ui_dialog.js';
import { jsonResponse } from './ui_dom.js';
import { translate as t } from './locales.js';
let activeStudio = null;
let pendingPlanLoad = null;
export function closePromptStudio(owner) {
pendingPlanLoad?.abort();
pendingPlanLoad = null;
if (!activeStudio) return;
const previous = activeStudio;
activeStudio = null;
previous.scope.dispose();
previous.owner.sidePromptComposerControl = null;
document.body.classList.remove('anomalous-prompt-studio-open');
if (!previous.owner.modal?.classList.contains('visible')) previous.owner.setTriggerVisible?.(true);
}
export async function openPromptStudio(owner = this) {
closePromptStudio(owner);
// Canvas Focus Mode: If left master browser is visible, auto-collapse it so canvas has 80%+ space
if (owner?.modal?.classList.contains('visible')) {
if (typeof owner.close === 'function') {
owner.close();
}
}
// Hide global floating trigger button while Studio Drawer is open
owner?.setTriggerVisible?.(false);
document.body.classList.add('anomalous-prompt-studio-open');
const overlay = document.createElement('div');
overlay.className = 'anomalous-prompt-studio-overlay';
const scope = createViewScope();
activeStudio = { scope, owner };
scope.onDispose(() => overlay.remove());
const drawer = document.createElement('aside');
drawer.className = 'anomalous-prompt-studio-drawer';
overlay.appendChild(drawer);
// Dock side persistence (default to left)
const savedSide = localStorage.getItem('anomalous_studio_dock_side') || 'left';
if (savedSide === 'left') {
drawer.classList.add('is-dock-left');
}
// Sidebar width persistence & resize handle (default to 580px)
const savedWidth = localStorage.getItem('anomalous_studio_sidebar_width') || '580';
drawer.style.width = `${Math.max(420, Math.min(window.innerWidth * 0.85, (parseInt(savedWidth, 10) || 580)))}px`;
const resizeHandle = document.createElement('div');
resizeHandle.className = 'anomalous-studio-resize-handle';
resizeHandle.title = window.anomalous_browser_lang === 'zh'
? '拖动调整侧边栏宽度,双击恢复默认'
: 'Drag to resize sidebar, double-click to reset';
drawer.appendChild(resizeHandle);
bindDrawerResize(resizeHandle, drawer, scope, {
side: () => drawer.classList.contains('is-dock-left') ? 'left' : 'right',
minWidth: 380,
setWidth: width => { drawer.style.width = `${width}px`; },
saveWidth: width => localStorage.setItem('anomalous_studio_sidebar_width', String(Math.round(width))),
});
resizeHandle.ondblclick = () => {
drawer.style.width = '580px';
localStorage.setItem('anomalous_studio_sidebar_width', '580');
};
const onKeydown = (e) => {
if (e.key === 'Escape') {
const inspector = document.querySelector('.anomalous-prompt-inspector-overlay');
if (inspector) return;
closePromptStudio(owner);
}
};
scope.listen(window, 'keydown', onKeydown);
const composer = createPromptWorkbench(owner, drawer, scope, {
onClose: () => closePromptStudio(owner),
onToggleDockSide: () => {
const isLeft = drawer.classList.toggle('is-dock-left');
localStorage.setItem('anomalous_studio_dock_side', isLeft ? 'left' : 'right');
return isLeft;
},
});
owner.sidePromptComposerControl = composer;
document.body.appendChild(overlay);
}
export async function showPromptComposer(owner, material) {
pendingPlanLoad?.abort();
if (material) {
const controller = new AbortController();
pendingPlanLoad = controller;
try {
if (owner.promptPlanDraft && !await anomalousConfirm(t('promptReplaceDraft'))) return;
if (controller.signal.aborted) return;
const response = await fetch(`/anomalous/material_full?include_workflow=0&filename=${encodeURIComponent(material.filename)}`, { signal: controller.signal });
const payload = await jsonResponse(response, 'material load failed');
if (controller.signal.aborted) return;
if (payload.data?.kind !== 'prompt_plan') throw new Error('invalid plan');
const draft = planToWorkbenchDraft(payload.data.plan || {}, payload.data.name || '');
draft.tags = payload.data.tags || [];
owner.promptPlanDraft = draft;
} catch (error) {
if (error.name !== 'AbortError') await anomalousAlert(t('materialDetailLoadError'));
return;
} finally {
if (pendingPlanLoad === controller) pendingPlanLoad = null;
}
}
owner.promptPlanDraft ||= newDraft();
await openPromptStudio(owner);
}