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 = ` -
- - - - +
+ +
-
${window.anomalous_browser_lang === 'zh' ? '点击下方词卡或拖拽到这里拼装' : 'Click cards below or drop here to assemble'}
-
${window.anomalous_browser_lang === 'zh' ? '支持自由调换次序,点击【智能排序】自动理顺' : 'Drag to reorder anytime, or click Smart Sort.'}
+
${window.anomalous_browser_lang === 'zh' ? '点击词卡或拖拽到这里拼装' : 'Click cards or drag here to assemble'}
+
${window.anomalous_browser_lang === 'zh' ? '支持自由拖拽调换次序,点击【智能排序】一键理顺' : 'Reorder freely anytime, or click Smart Sort.'}
`; // Accept drops on empty dropzone setupDropzoneListeners(dropzoneNotice); diff --git a/web/styles.css b/web/styles.css index c0bc04c..6eb8844 100644 --- a/web/styles.css +++ b/web/styles.css @@ -9541,31 +9541,40 @@ dialog.anomalous-gallery-viewer::backdrop { display: flex; align-items: center; justify-content: space-between; - flex-wrap: wrap; + flex-wrap: nowrap !important; gap: 8px; - padding-bottom: 6px; + height: 44px; + padding: 0 12px; border-bottom: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(20, 22, 27, 0.95); + box-sizing: border-box; + flex-shrink: 0; } .anomalous-prompt-topbar-left { display: flex; align-items: center; gap: 8px; + min-width: 0; + flex: 1 1 auto; } .anomalous-prompt-topbar-left h3 { margin: 0; - font-size: 0.98rem; - font-weight: 600; + font-size: 0.92rem; + font-weight: 700; color: #f8fafc; letter-spacing: 0.02em; + white-space: nowrap; + flex-shrink: 0; } .anomalous-prompt-topbar-actions { display: flex; align-items: center; - gap: 6px; - flex-wrap: wrap; + gap: 4px; + flex-shrink: 0; + flex-wrap: nowrap !important; } .anomalous-prompt-close-btn { @@ -9585,16 +9594,16 @@ dialog.anomalous-gallery-viewer::backdrop { background: var(--amb-bg-input, rgba(0, 0, 0, 0.35)); border: 1px solid var(--amb-border, rgba(255, 255, 255, 0.14)); border-radius: var(--amb-radius-control, 6px); - padding: 3px 8px; + padding: 2px 8px; height: 26px; color: var(--amb-text-main, #f1f5f9); font-size: 0.78rem; outline: none; transition: border-color 0.15s ease, box-shadow 0.15s ease; box-sizing: border-box; - flex: 1 1 160px; - min-width: 100px; - max-width: 240px; + flex: 0 1 130px; + min-width: 60px; + max-width: 140px; } .anomalous-prompt-name-input:focus { @@ -12830,7 +12839,7 @@ button:hover .anomalous-btn-icon { position: absolute; top: 0; right: 0; - width: 520px; + width: 580px; max-width: 90vw; height: 100vh; background: var(--amb-bg-page, #121316); @@ -12874,7 +12883,7 @@ button:hover .anomalous-btn-icon { box-shadow: 0 0 10px rgba(56, 189, 248, 0.6); } -/* Drawer Workbench Grid */ +/* Drawer Workbench Base */ .anomalous-prompt-studio-drawer .anomalous-prompt-workbench { background: transparent; padding: 0; @@ -12883,40 +12892,190 @@ button:hover .anomalous-btn-icon { flex-direction: column; } -.anomalous-prompt-studio-drawer .anomalous-prompt-workbench-grid { +/* Left Dock Mode (Default): Left Column = Cards (190px), Right Column = Assembly Track (faces canvas on right) */ +.anomalous-prompt-studio-drawer.is-dock-left .anomalous-prompt-workbench-grid { display: grid !important; - grid-template-columns: 145px minmax(0, 1fr) !important; + grid-template-columns: 190px minmax(0, 1fr) !important; flex-direction: row !important; - gap: 8px !important; - padding: 6px 8px 8px !important; + gap: 0 !important; + padding: 0 !important; flex: 1; min-height: 0; } -.anomalous-prompt-studio-drawer .anomalous-workbench-left-panel { +.anomalous-prompt-studio-drawer.is-dock-left .anomalous-workbench-left-panel { order: 1 !important; flex: none !important; - width: 145px !important; + width: 190px !important; max-height: 100% !important; - padding: 6px 6px 8px !important; + padding: 8px 10px !important; + border-right: 1px solid rgba(255, 255, 255, 0.08) !important; + border-left: none !important; + background: rgba(18, 20, 24, 0.7) !important; + box-sizing: border-box; + display: flex; + flex-direction: column; } -.anomalous-prompt-studio-drawer .anomalous-workbench-right-panel { +.anomalous-prompt-studio-drawer.is-dock-left .anomalous-workbench-right-panel { order: 2 !important; flex: 1 !important; max-height: 100% !important; - padding: 6px 8px 48px !important; /* reserve bottom space for floating dock */ + padding: 8px 12px 52px !important; /* reserve bottom space for floating dock */ position: relative; + box-sizing: border-box; + background: rgba(14, 15, 18, 0.5) !important; + display: flex; + flex-direction: column; +} + +/* Right Dock Mode: Right Column = Cards (190px), Left Column = Assembly Track (faces canvas on left) */ +.anomalous-prompt-studio-drawer:not(.is-dock-left) .anomalous-prompt-workbench-grid { + display: grid !important; + grid-template-columns: minmax(0, 1fr) 190px !important; + flex-direction: row !important; + gap: 0 !important; + padding: 0 !important; + flex: 1; + min-height: 0; +} + +.anomalous-prompt-studio-drawer:not(.is-dock-left) .anomalous-workbench-right-panel { + order: 1 !important; + flex: 1 !important; + max-height: 100% !important; + padding: 8px 12px 52px !important; + position: relative; + box-sizing: border-box; + border-right: 1px solid rgba(255, 255, 255, 0.08) !important; + border-left: none !important; + background: rgba(14, 15, 18, 0.5) !important; + display: flex; + flex-direction: column; +} + +.anomalous-prompt-studio-drawer:not(.is-dock-left) .anomalous-workbench-left-panel { + order: 2 !important; + flex: none !important; + width: 190px !important; + max-height: 100% !important; + padding: 8px 10px !important; + border-left: none !important; + border-right: none !important; + background: rgba(18, 20, 24, 0.7) !important; + box-sizing: border-box; + display: flex; + flex-direction: column; +} + +/* Column Headers & Action Buttons */ +.anomalous-workbench-col-header { + display: flex; + align-items: center; + justify-content: space-between; + height: 30px; + padding: 0 2px; + margin-bottom: 6px; + flex-shrink: 0; +} + +.anomalous-workbench-col-title { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 0.84rem; + font-weight: 600; + color: #f1f5f9; + white-space: nowrap; +} + +.anomalous-workbench-col-title .anomalous-sub-counter { + font-size: 0.72rem; + color: #64748b; + font-weight: normal; +} + +.anomalous-workbench-header-actions { + display: inline-flex; + align-items: center; + gap: 4px; + flex-shrink: 0; +} + +.anomalous-workbench-header-actions button.anomalous-icon-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + border-radius: 5px; + color: #94a3b8; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); + cursor: pointer; + transition: all 0.15s ease; +} + +.anomalous-workbench-header-actions button.anomalous-icon-btn:hover { + color: #ffffff; + background: rgba(56, 189, 248, 0.18); + border-color: rgba(56, 189, 248, 0.4); +} + +/* Category Pills: 5 equal 2-char chips */ +.anomalous-workbench-category-pills { + display: flex; + align-items: center; + gap: 3px; + flex-wrap: nowrap; + margin: 4px 0 8px; +} + +.anomalous-workbench-pill { + flex: 1 1 0; + text-align: center; + padding: 3px 0; + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.03); + color: #94a3b8; + font-size: 0.72rem; + cursor: pointer; + transition: all 0.15s ease; + line-height: 1.3; + white-space: nowrap; +} + +.anomalous-workbench-pill:hover { + color: #f1f5f9; + background: rgba(255, 255, 255, 0.08); +} + +.anomalous-workbench-pill.is-active { + color: #38bdf8; + background: rgba(56, 189, 248, 0.15); + border-color: rgba(56, 189, 248, 0.4); + font-weight: 600; +} + +/* Compact Source Cards in 190px */ +.anomalous-source-cards-list { + flex: 1; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 4px; + padding-right: 2px; } -/* Compact Source Cards */ .anomalous-source-card-compact { display: flex; align-items: center; gap: 6px; - padding: 4px 8px; - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.08); + padding: 5px 8px; + background: rgba(255, 255, 255, 0.035); + border: 1px solid rgba(255, 255, 255, 0.07); border-radius: 6px; cursor: pointer; transition: all 0.15s ease; @@ -12926,27 +13085,31 @@ button:hover .anomalous-btn-icon { } .anomalous-source-card-compact:hover { - background: rgba(56, 189, 248, 0.15); - border-color: rgba(56, 189, 248, 0.4); + background: rgba(56, 189, 248, 0.12); + border-color: rgba(56, 189, 248, 0.35); transform: translateX(2px); } .anomalous-source-card-dot { - width: 7px; - height: 7px; + width: 6px; + height: 6px; border-radius: 50%; flex-shrink: 0; } .anomalous-source-card-name { - font-size: 0.74rem; - color: #e2e8f0; + font-size: 0.75rem; + color: #cbd5e1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; } +.anomalous-source-card-compact:hover .anomalous-source-card-name { + color: #ffffff; +} + .anomalous-source-card-add-icon { font-size: 0.72rem; color: #64748b; @@ -12960,6 +13123,30 @@ button:hover .anomalous-btn-icon { color: #38bdf8; } +/* Assembly Dropzone Notice */ +.anomalous-assembly-dropzone { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex: 1; + min-height: 260px; + padding: 40px 20px; + border: 1px dashed rgba(255, 255, 255, 0.1); + border-radius: 12px; + background: rgba(255, 255, 255, 0.015); + color: #64748b; + text-align: center; + box-sizing: border-box; + transition: all 0.2s ease; +} + +.anomalous-assembly-dropzone:hover, +.anomalous-assembly-dropzone.is-drag-over { + border-color: rgba(56, 189, 248, 0.4); + background: rgba(56, 189, 248, 0.04); +} + /* Weight Wheel Pill in Assembled Blocks */ .anomalous-block-weight-pill { display: inline-flex;