149 lines
6.0 KiB
JavaScript
149 lines
6.0 KiB
JavaScript
import { createViewScope, bindDrawerResize } from './ui_lifecycle.js';
|
|
import { createPromptWorkbench } from './ui_prompt_workbench.js';
|
|
import { newDraft, normalizeBlock, syncDraftSynthesizedText } from './prompt_studio_data.js';
|
|
import { categorizePromptSnippet, 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);
|
|
}
|
|
|
|
export function appendPromptToStudio(owner, textSnippet, isPositive = true, noteTitle = '') {
|
|
if (!textSnippet || !textSnippet.trim()) return;
|
|
const role = isPositive ? 'positive' : 'negative';
|
|
const cat = isPositive ? categorizePromptSnippet(textSnippet) : 'base';
|
|
const block = normalizeBlock({
|
|
title: noteTitle || (window.anomalous_browser_lang === 'zh' ? '素材片段' : 'Material Snippet'),
|
|
content: textSnippet.trim(),
|
|
role,
|
|
category: cat,
|
|
});
|
|
|
|
if (owner.sidePromptComposerControl?.addBlock) {
|
|
owner.sidePromptComposerControl.addBlock(block);
|
|
|
|
} else {
|
|
owner.promptPlanDraft ||= newDraft();
|
|
owner.promptPlanDraft.plan.parts ||= [];
|
|
owner.promptPlanDraft.plan.parts.push(block);
|
|
syncDraftSynthesizedText(owner.promptPlanDraft);
|
|
if (!owner.promptPlanDraft.name?.trim() && noteTitle) owner.promptPlanDraft.name = noteTitle;
|
|
}
|
|
}
|
|
|