diff --git a/style.css b/style.css index 317e552..dc9ffa8 100644 --- a/style.css +++ b/style.css @@ -2683,15 +2683,15 @@ } .bme-task-editor-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + display: flex; + flex-direction: column; gap: 14px; align-items: start; } .bme-task-editor-grid > .bme-config-card:last-child { - position: sticky; - top: 0; + position: static; + top: auto; } .bme-task-regex-top { @@ -2812,6 +2812,260 @@ line-height: 1.55; } +/* ═══════ Single-column Block Row (EW-style) ═══════ */ + +.bme-task-block-rows { + display: flex; + flex-direction: column; + gap: 4px; +} + +.bme-task-block-row { + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 10px; + background: rgba(255, 255, 255, 0.025); + transition: border-color 0.15s, box-shadow 0.15s; +} + +.bme-task-block-row:hover { + border-color: rgba(255, 255, 255, 0.12); +} + +.bme-task-block-row.is-expanded { + border-color: var(--bme-primary); + box-shadow: 0 0 0 1px var(--bme-primary); +} + +.bme-task-block-row.is-disabled { + opacity: 0.5; +} + +.bme-task-block-row.dragging { + opacity: 0.35; +} + +.bme-task-block-row.drag-over-top { + border-top: 2px solid var(--bme-primary); + margin-top: -1px; +} + +.bme-task-block-row.drag-over-bottom { + border-bottom: 2px solid var(--bme-primary); + margin-bottom: -1px; +} + +/* Row header */ +.bme-task-block-row-header { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + min-height: 44px; + cursor: pointer; + user-select: none; + flex-wrap: wrap; +} + +/* Drag handle */ +.bme-task-drag-handle { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + color: var(--bme-on-surface-dim); + opacity: 0.45; + cursor: grab; + flex-shrink: 0; + transition: opacity 0.15s; +} + +.bme-task-drag-handle:hover { + opacity: 1; +} + +.bme-task-drag-handle:active { + cursor: grabbing; +} + +/* Block type icon */ +.bme-task-block-icon { + display: flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + font-size: 14px; + flex-shrink: 0; + color: var(--bme-on-surface-dim); +} + +/* Block name */ +.bme-task-block-name { + flex: 1; + min-width: 0; + font-size: 13px; + font-weight: 600; + color: var(--bme-on-surface); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Badges (role / inject) */ +.bme-task-block-badge { + display: inline-flex; + align-items: center; + font-size: 10px; + font-weight: 700; + padding: 2px 7px; + border-radius: 4px; + background: rgba(255, 255, 255, 0.07); + color: var(--bme-on-surface-dim); + white-space: nowrap; + flex-shrink: 0; + text-transform: lowercase; +} + +.bme-badge-role-system { + background: rgba(140, 140, 160, 0.18); + color: #b0b0c0; +} + +.bme-badge-role-user { + background: rgba(160, 100, 220, 0.2); + color: #c8a0f0; +} + +.bme-badge-role-assistant { + background: rgba(80, 180, 120, 0.2); + color: #80d0a0; +} + +/* Row spacer */ +.bme-task-block-row-spacer { + flex: 1; +} + +/* Row action buttons (edit / delete) */ +.bme-task-row-btn { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--bme-on-surface-dim); + cursor: pointer; + flex-shrink: 0; + font-size: 13px; + transition: background 0.12s, color 0.12s; +} + +.bme-task-row-btn:hover { + background: rgba(255, 255, 255, 0.08); + color: var(--bme-on-surface); +} + +.bme-task-row-btn-danger:hover { + background: rgba(220, 80, 80, 0.15); + color: #f08080; +} + +/* Inline toggle switch (EW-style yellow) */ +.bme-task-row-toggle { + position: relative; + display: inline-flex; + align-items: center; + width: 38px; + height: 22px; + flex-shrink: 0; + cursor: pointer; +} + +.bme-task-row-toggle input { + position: absolute; + opacity: 0; + width: 0; + height: 0; +} + +.bme-task-row-toggle-slider { + position: absolute; + inset: 0; + border-radius: 999px; + background: rgba(255, 255, 255, 0.12); + transition: background 0.2s; +} + +.bme-task-row-toggle-slider::before { + content: ""; + position: absolute; + left: 3px; + top: 3px; + width: 16px; + height: 16px; + border-radius: 50%; + background: #888; + transition: transform 0.2s, background 0.2s; +} + +.bme-task-row-toggle input:checked + .bme-task-row-toggle-slider { + background: rgba(220, 180, 50, 0.35); +} + +.bme-task-row-toggle input:checked + .bme-task-row-toggle-slider::before { + transform: translateX(16px); + background: #e0b830; +} + +/* Expand editor area */ +.bme-task-block-expand { + border-top: 1px solid rgba(255, 255, 255, 0.06); + padding: 12px; + animation: bme-block-expand-in 0.2s ease; +} + +.bme-task-expand-row2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-bottom: 10px; +} + +.bme-task-block-expand .bme-config-row { + margin-bottom: 8px; +} + +.bme-task-block-expand .bme-config-row:last-child { + margin-bottom: 0; +} + +.bme-task-block-expand .bme-config-textarea { + min-height: 120px; + max-height: 400px; + resize: vertical; +} + +.bme-task-expand-footer { + display: flex; + justify-content: flex-end; + margin-top: 8px; +} + +@keyframes bme-block-expand-in { + from { + opacity: 0; + max-height: 0; + } + to { + opacity: 1; + max-height: 800px; + } +} + + .bme-regex-preview-screen { gap: 14px; } @@ -3670,7 +3924,7 @@ .bme-config-grid-2, .bme-theme-card-grid, .bme-task-field-grid, - .bme-task-editor-grid, + .bme-task-expand-row2, .bme-task-regex-top, .bme-task-debug-grid { grid-template-columns: 1fr; diff --git a/ui/panel.js b/ui/panel.js index 4feb80b..4c91ffc 100644 --- a/ui/panel.js +++ b/ui/panel.js @@ -280,6 +280,7 @@ let currentConfigSectionId = "toggles"; let currentTaskProfileTaskType = "extract"; let currentTaskProfileTabId = "generation"; let currentTaskProfileBlockId = ""; +let currentTaskProfileDragBlockId = ""; let currentTaskProfileRuleId = ""; let showGlobalRegexPanel = false; let currentGlobalRegexRuleId = ""; @@ -4998,6 +4999,67 @@ function _bindTaskProfileWorkspace() { workspace.addEventListener("change", (event) => { _handleTaskProfileWorkspaceChange(event); }); + workspace.addEventListener("dragstart", (event) => { + const target = event.target; + if (!(target instanceof HTMLElement)) return; + const handle = target.closest(".bme-task-drag-handle"); + const row = target.closest(".bme-task-block-row"); + if (!handle || !(row instanceof HTMLElement)) return; + const blockId = String(row.dataset.blockId || "").trim(); + if (!blockId) return; + currentTaskProfileDragBlockId = blockId; + if (event.dataTransfer) { + event.dataTransfer.effectAllowed = "move"; + event.dataTransfer.dropEffect = "move"; + event.dataTransfer.setData("text/plain", blockId); + } + window.requestAnimationFrame(() => { + row.classList.add("dragging"); + }); + }); + workspace.addEventListener("dragover", (event) => { + const target = event.target; + if (!(target instanceof HTMLElement) || !currentTaskProfileDragBlockId) return; + const row = target.closest(".bme-task-block-row"); + if (!(row instanceof HTMLElement)) return; + event.preventDefault(); + if (event.dataTransfer) { + event.dataTransfer.dropEffect = "move"; + } + const position = _getTaskBlockDropPosition(row, event.clientY); + _setTaskBlockDragIndicator(workspace, row, position); + }); + workspace.addEventListener("dragleave", (event) => { + const target = event.target; + if (!(target instanceof HTMLElement)) return; + const row = target.closest(".bme-task-block-row"); + if (!(row instanceof HTMLElement)) return; + const relatedTarget = event.relatedTarget; + if (relatedTarget instanceof Node && row.contains(relatedTarget)) { + return; + } + row.classList.remove("drag-over-top", "drag-over-bottom"); + }); + workspace.addEventListener("drop", (event) => { + const target = event.target; + if (!(target instanceof HTMLElement)) return; + const row = target.closest(".bme-task-block-row"); + if (!(row instanceof HTMLElement)) return; + event.preventDefault(); + const sourceId = + currentTaskProfileDragBlockId || + String(event.dataTransfer?.getData("text/plain") || "").trim(); + const targetId = String(row.dataset.blockId || "").trim(); + const position = _getTaskBlockDropPosition(row, event.clientY); + _clearTaskBlockDragIndicators(workspace); + currentTaskProfileDragBlockId = ""; + if (!sourceId || !targetId || sourceId === targetId) return; + _reorderTaskBlocks(sourceId, targetId, position); + }); + workspace.addEventListener("dragend", () => { + currentTaskProfileDragBlockId = ""; + _clearTaskBlockDragIndicators(workspace); + }); workspace.dataset.bmeBound = "true"; } @@ -5327,7 +5389,7 @@ function _getTaskProfileWorkspaceState(settings = _getSettings?.() || {}) { ? profile.regex.localRules : []; - if (!blocks.some((block) => block.id === currentTaskProfileBlockId)) { + if (currentTaskProfileBlockId && !blocks.some((block) => block.id === currentTaskProfileBlockId)) { currentTaskProfileBlockId = blocks[0]?.id || ""; } if (!regexRules.some((rule) => rule.id === currentTaskProfileRuleId)) { @@ -6035,6 +6097,21 @@ async function _handleTaskProfileWorkspaceClick(event) { currentTaskProfileBlockId = actionEl.dataset.blockId || ""; _refreshTaskProfileWorkspace(); return; + case "toggle-block-expand": { + // Ignore if the click originated from a toggle switch, delete button, or drag handle + const originEl = event.target; + if (originEl.closest(".bme-task-row-toggle") || originEl.closest(".bme-task-row-btn-danger") || originEl.closest(".bme-task-drag-handle")) { + return; + } + const blockId = actionEl.dataset.blockId || ""; + if (currentTaskProfileBlockId === blockId) { + currentTaskProfileBlockId = ""; + } else { + currentTaskProfileBlockId = blockId; + } + _refreshTaskProfileWorkspace(); + return; + } case "select-regex-rule": if (_isGlobalRegexPanelTarget(actionEl)) { currentGlobalRegexRuleId = actionEl.dataset.ruleId || ""; @@ -6085,6 +6162,16 @@ async function _handleTaskProfileWorkspaceClick(event) { return { selectBlockId: block.id }; }); return; + case "toggle-block-enabled-cb": + _updateCurrentTaskProfile((draft) => { + const blocks = _sortTaskBlocks(draft.blocks); + const block = blocks.find((item) => item.id === actionEl.dataset.blockId); + if (!block) return null; + block.enabled = actionEl.checked; + draft.blocks = _normalizeTaskBlocks(blocks); + return { selectBlockId: currentTaskProfileBlockId }; + }); + return; case "delete-block": _deleteTaskBlock(actionEl.dataset.blockId); return; @@ -6365,57 +6452,40 @@ function _renderTaskProfileWorkspace(state) { } function _renderTaskPromptTab(state) { return ` -
-
-
-
-
Prompt 块列表
-
- 通过顺序、启停与角色控制最终请求的编排方式。 +
+
+ + + + +
+ ${state.blocks.length} 个块 +
+ +
+ ${state.blocks.length + ? state.blocks + .map((block, index) => _renderTaskBlockRow(block, index, state)) + .join("") + : ` +
+ 当前预设还没有块。可以先新增一个自定义块或内置块。
-
-
- -
-
- - - - -
- ${state.blocks.length} 个块 -
- -
- ${state.blocks.length - ? state.blocks - .map((block, index) => _renderTaskBlockListItem(block, index, state)) - .join("") - : ` -
- 当前预设还没有块。可以先新增一个自定义块或内置块。 -
- `} -
-
- -
- ${_renderTaskBlockEditor(state)} -
+ `}
`; } @@ -7388,73 +7458,93 @@ function _stringifyDebugValue(value) { } } -function _renderTaskBlockListItem(block, index, state) { - const isSelected = block.id === state.selectedBlock?.id; +function _getBlockTypeIcon(type) { + switch (type) { + case "builtin": return ``; + case "legacyPrompt": return ``; + default: return ``; + } +} + +function _getInjectModeLabel(mode) { + switch (mode) { + case "append": return "追加"; + case "relative": + default: return "相对"; + } +} + +function _renderTaskBlockRow(block, index, state) { + const isExpanded = block.id === state.selectedBlock?.id; + const roleClass = `bme-badge-role-${block.role || "system"}`; + const disabledClass = block.enabled ? "" : "is-disabled"; + const expandedClass = isExpanded ? "is-expanded" : ""; + return ` -
- -
+ + ${_getBlockTypeIcon(block.type)} + + + ${_escHtml(block.name || _getTaskBlockTypeLabel(block.type))} + + + ${_escHtml(block.role || "system")} + + + ${_escHtml(_getInjectModeLabel(block.injectionMode))} + + - - +
+ ${isExpanded ? ` +
+ ${_renderTaskBlockInlineEditor(block, state)} +
+ ` : ""}
`; } -function _renderTaskBlockEditor(state) { - const block = state.selectedBlock; - if (!block) { - return ` -
块详情
-
从左侧列表选择一个块进行编辑。
- `; - } - +function _renderTaskBlockInlineEditor(block, state) { const builtinOptions = state.builtinBlockDefinitions .map( (item) => ` @@ -7474,31 +7564,18 @@ function _renderTaskBlockEditor(state) { : block.content || ""; return ` -
-
-
块详情
-
- 当前块会直接写回到任务预设中。 -
-
- ${_escHtml(_getTaskBlockTypeLabel(block.type))} - ${block.type === "builtin" ? _helpTip( - (state.builtinBlockDefinitions.find((d) => d.sourceKey === block.sourceKey) || {}).description || "" - ) : ""} -
-
- +
-
+
- - ${ block.type === "builtin" ? (() => { @@ -7552,18 +7617,17 @@ function _renderTaskBlockEditor(state) { const externalLabel = externalSourceMap[block.sourceKey]; return `
- +
${externalLabel - ? `
- 此提示词的内容是从其他地方提取的,无法在此处进行编辑。
- 来源:${externalLabel} + ? `
+ 内容来源:${externalLabel},无法在此编辑。
` : `
- +