From 96c340b79b5cb0a6777b646ab7b8da2d3f211f53 Mon Sep 17 00:00:00 2001 From: DemonGatanjieu <1027066412@qq.com> Date: Mon, 14 Sep 2026 21:44:05 +0800 Subject: [PATCH] style(prompt-studio): default to left dock, auto-orient track facing canvas, widen cards column to 190px and eliminate topbar wrapping --- ARCHITECTURE.md | 11 +- web/modules/ui_prompt_composer.js | 79 ++++------ web/styles.css | 249 ++++++++++++++++++++++++++---- 3 files changed, 256 insertions(+), 83 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 977e9b6..b799b9b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -193,11 +193,12 @@ DOM or live LiteGraph state. - **Floating Trigger Button & Anomalous Hypercube Core (悬浮入口魔晶重构)**: - 重绘旧版扁平纸箱立方体为 **「异象超维魔方核 (Anomalous Hypercube Core)」**:外层等轴测立体超维立方体轮廓,内嵌悬浮发光微晶棱镜(`--amb-accent` 随主题自适应为天青/猩红),核心凝聚一枚四芒星辉奇点(`✦`),兼具 3D 模型容器特征与魔法档案馆星辰意象。 - 悬浮球体容器 (`#anomalous-trigger-btn`) 升级为亚克力磨砂深色毛玻璃材质 (`backdrop-filter: blur(16px)`),悬停微移提亮,图标伴随 12° 优雅微旋与天青/猩红柔和微光,保持高度克制的同时极具辨识度与高级质感。 - - **Prompt Studio Dual-Column Workbench (提示词工坊两行顶栏与容器自适应)**: - - Topbar restructured into two clean rows: Row 1 hosts Back, shortened title ("提示词组合"), New, "更多 ▾" dropdown (containing Export), and primary Save Plan; Row 2 hosts expanded preset name and tag inputs. - - Insert position ("添加到前面/后面") relocated into the target node write bar, adjacent to widget selection and write triggers where execution actually happens. - - Left Column ("词卡库"): 280-320px width, clean cards with explicit text role badges (`[正向]` / `[负向]`), 2-line preview, single top drag hint, and secondary node/material extract buttons. - - Responsive Container Query: Uses `@container prompt-workbench (max-width: 850px)` to smoothly stack panels vertically in narrow dock/floating windows without clipping or forced 100% heights. + - **Prompt Studio Dual-Column Workbench (提示词工坊侧边抽屉与双向自适应停靠)**: + - **Default Left Docking & Canvas-Facing Flow**: Defaults to left dock (`is-dock-left`), with Source Cards (190px) on the outer screen edge and Assembly Track on the inner side directly facing the ComfyUI canvas. + - **Bidirectional Column Flipping**: When toggled to right dock, the layout seamlessly swaps column visual order (`order: 1` for Assembly Track facing canvas on left, `order: 2` for Source Cards along right screen edge) so that the Assembly Track is ALWAYS adjacent to the ComfyUI canvas regardless of docking side. + - **Single-Line Unbroken Topbar**: Topbar strictly enforces `flex-wrap: nowrap`, keeping Title, Preset Name input (constrained max-width 140px), Node Extraction (`🎯`), Dock Toggle (`→`/`←`), More dropdown (`··· 更多`), and Close (`✕`) in a unified single row under any drawer width. + - **De-cluttered Left Panel**: Expanded left column from 145px to 190px, purged redundant text buttons into compact 24x24 icon buttons (`[📥]` and `[+]`), shortened category filter pills to clean 2-character chips (`[全部]`, `[底模]`, `[风格]`, `[主体]`, `[触发]`) fitting into a single row, and removed bulky instructional tip banners. + - **Dynamic Floating Action Dock & Popout Modal**: Floating Action Dock slides up only when blocks exist; provides single-click Node Direct Write and popout full-screen Prompt Inspector (`openPromptInspectorModal`) for deep multi-line tuning. - **Uniform Card Skeletons**: Recipe and material cards enforce "Preview -> 2-Line Clamped Title -> Key Specs -> Footer Action Bar" order, preventing Chinese title clipping; Model cards display titles and filenames at rest without `translateY` collapse and without `backdrop-filter: blur`, fading in action buttons on hover. - **Custom Tooltip System (`[data-tooltip]`)**: Solid dark capsules (`rgba(18, 20, 26, 0.98)`), 350ms standard hover delay, and keyboard focus-visible support. diff --git a/web/modules/ui_prompt_composer.js b/web/modules/ui_prompt_composer.js index fb9a721..5e12bc3 100644 --- a/web/modules/ui_prompt_composer.js +++ b/web/modules/ui_prompt_composer.js @@ -345,15 +345,15 @@ export async function openPromptStudio(owner = this) { activeStudioDrawer = drawer; overlay.appendChild(drawer); - // Dock side persistence - const savedSide = localStorage.getItem('anomalous_studio_dock_side') || 'right'; + // 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 - const savedWidth = localStorage.getItem('anomalous_studio_sidebar_width') || '520'; - drawer.style.width = `${Math.max(400, Math.min(window.innerWidth * 0.85, parseInt(savedWidth, 10)))}px`; + // 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)))}px`; const resizeHandle = document.createElement('div'); resizeHandle.className = 'anomalous-studio-resize-handle'; @@ -403,8 +403,8 @@ export async function openPromptStudio(owner = this) { }; resizeHandle.ondblclick = () => { - drawer.style.width = '520px'; - localStorage.setItem('anomalous_studio_sidebar_width', '520'); + drawer.style.width = '580px'; + localStorage.setItem('anomalous_studio_sidebar_width', '580'); }; const onKeydown = (e) => { @@ -596,10 +596,12 @@ function buildPromptComposer(owner, container, options = {}) { const dockSideBtn = text(topActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm'); const isLeft = () => activeStudioDrawer?.classList.contains('is-dock-left'); const updateDockBtn = () => { - dockSideBtn.innerHTML = isLeft() ? '⇥' : '⇤'; + dockSideBtn.innerHTML = isLeft() + ? `` + : ``; dockSideBtn.title = isLeft() - ? (window.anomalous_browser_lang === 'zh' ? '停靠至右侧' : 'Dock Right') - : (window.anomalous_browser_lang === 'zh' ? '停靠至左侧' : 'Dock Left'); + ? (window.anomalous_browser_lang === 'zh' ? '停靠至右侧' : 'Dock to Right') + : (window.anomalous_browser_lang === 'zh' ? '停靠至左侧' : 'Dock to Left'); }; updateDockBtn(); dockSideBtn.onclick = () => { @@ -708,25 +710,20 @@ function buildPromptComposer(owner, container, options = {}) { // Action button group in leftHeader const leftHeaderActions = text(leftHeader, 'div', '', 'anomalous-workbench-header-actions'); - // Button 1: Extract Prompts from Selected Canvas Node - const extractNodeBtn = text(leftHeaderActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm anomalous-btn-extract-node'); - extractNodeBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '提取' : 'Extract'}`; - extractNodeBtn.title = window.anomalous_browser_lang === 'zh' ? '从画布当前选中节点提取提示词并生成词卡' : 'Extract prompt text from selected canvas node into cards'; + // Button 1: One-click Sync / Import from Material Library + const importMaterialsBtn = text(leftHeaderActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm anomalous-icon-btn'); + importMaterialsBtn.innerHTML = ``; + importMaterialsBtn.title = window.anomalous_browser_lang === 'zh' ? '从素材库同步提示词' : 'Sync prompts from Material Library'; - // Button 2: One-click Sync / Import from Material Library - const importMaterialsBtn = text(leftHeaderActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm'); - importMaterialsBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '导入' : 'Import'}`; - importMaterialsBtn.title = window.anomalous_browser_lang === 'zh' ? '读取素材库,一键把素材库里沉淀的提示词捞入当前工坊词库' : 'Read and import all prompts from Material Library into workbench'; - - // Button 3: Create New Custom Card (placed in header right, never wraps) - const newCardTriggerBtn = text(leftHeaderActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm'); - newCardTriggerBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '新建' : 'New'}`; - newCardTriggerBtn.title = window.anomalous_browser_lang === 'zh' ? '新建并保存一张提示词卡片' : 'Create a new prompt card'; + // Button 2: Create New Custom Card + const newCardTriggerBtn = text(leftHeaderActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm anomalous-icon-btn'); + newCardTriggerBtn.innerHTML = ``; + newCardTriggerBtn.title = window.anomalous_browser_lang === 'zh' ? '新建词卡' : 'Create new prompt card'; // Search and category filters bar const leftFilterBar = text(leftPanel, 'div', '', 'anomalous-workbench-filter-bar'); const leftSearch = text(leftFilterBar, 'input', '', 'anomalous-workbench-search-input'); - leftSearch.placeholder = window.anomalous_browser_lang === 'zh' ? '搜索提示词或分类...' : 'Search prompt snippet...'; + leftSearch.placeholder = window.anomalous_browser_lang === 'zh' ? '搜索词卡...' : 'Search cards...'; leftSearch.oninput = () => { sourceFilterKeyword = leftSearch.value.trim().toLowerCase(); renderSourceCardsList(); @@ -735,10 +732,10 @@ function buildPromptComposer(owner, container, options = {}) { const leftCategoryPills = text(leftPanel, 'div', '', 'anomalous-workbench-category-pills'); const filterCats = [ { id: 'all', label: window.anomalous_browser_lang === 'zh' ? '全部' : 'All' }, - { id: 'base', label: window.anomalous_browser_lang === 'zh' ? '通用底模' : 'Base' }, - { id: 'style', label: window.anomalous_browser_lang === 'zh' ? '风格氛围' : 'Style' }, - { id: 'subject', label: window.anomalous_browser_lang === 'zh' ? '主体内容' : 'Subject' }, - { id: 'trigger', label: window.anomalous_browser_lang === 'zh' ? '触发词' : 'Trigger' }, + { id: 'base', label: window.anomalous_browser_lang === 'zh' ? '底模' : 'Base' }, + { id: 'style', label: window.anomalous_browser_lang === 'zh' ? '风格' : 'Style' }, + { id: 'subject', label: window.anomalous_browser_lang === 'zh' ? '主体' : 'Subject' }, + { id: 'trigger', label: window.anomalous_browser_lang === 'zh' ? '触发' : 'Trigger' }, ]; filterCats.forEach(cat => { const pill = text(leftCategoryPills, 'button', cat.label, `anomalous-workbench-pill${sourceFilterCategory === cat.id ? ' is-active' : ''}`); @@ -754,15 +751,6 @@ function buildPromptComposer(owner, container, options = {}) { const newCardForm = text(leftPanel, 'div', '', 'anomalous-workbench-new-card-form'); newCardForm.style.display = 'none'; - // Usage Tip Banner - const tipBanner = text(leftPanel, 'div', '', 'anomalous-workbench-tip-banner'); - tipBanner.innerHTML = ` - - ${window.anomalous_browser_lang === 'zh' ? '点击词卡或拖拽至右侧拼装台,自由调换顺序' : 'Click card or drag to right stage to assemble'} - `; - // Source Cards List const sourceCardsList = text(leftPanel, 'div', '', 'anomalous-source-cards-list'); @@ -1021,7 +1009,6 @@ function buildPromptComposer(owner, container, options = {}) { } } - extractNodeBtn.onclick = () => extractPromptsFromSelectedNode(false); rightSuckNodeBtn.onclick = () => extractPromptsFromSelectedNode(true); let materialSyncController = null; @@ -1034,7 +1021,7 @@ function buildPromptComposer(owner, container, options = {}) { materialSyncController = controller; importMaterialsBtn.disabled = true; - importMaterialsBtn.textContent = window.anomalous_browser_lang === 'zh' ? '正在同步素材...' : 'Syncing...'; + importMaterialsBtn.innerHTML = `⏳`; try { const res = await fetch('/anomalous/materials?category=prompts&limit=150', { signal: controller.signal }); @@ -1084,7 +1071,7 @@ function buildPromptComposer(owner, container, options = {}) { if (materialSyncController === controller) { materialSyncController = null; importMaterialsBtn.disabled = false; - importMaterialsBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '导入' : 'Import'}`; + importMaterialsBtn.innerHTML = ``; } } } @@ -1428,15 +1415,13 @@ function buildPromptComposer(owner, container, options = {}) { if (!currentRoleParts.length) { const dropzoneNotice = text(blocksContainer, 'div', '', 'anomalous-assembly-dropzone'); dropzoneNotice.innerHTML = ` -