Files
DemonGatanjieu-Anomalous_Mo…/web/modules/ui_prompt_composer.js
T

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;
}
}