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>
125 lines
5.0 KiB
JavaScript
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);
|
|
}
|